2026FIC取证决赛wp(手机取证)

手机取证

这套题的手机取证真难啊,感谢羊羊羊和mumuzi的博客

复制代码
https://mumuzi.blog/docs/Forensic/%E5%8F%96%E8%AF%81%E6%AF%94%E8%B5%9BWP/07-2026%E7%AC%AC%E5%85%AD%E5%B1%8AFIC%E5%86%B3%E8%B5%9B/%E6%89%8B%E6%9C%BA%E5%8F%96%E8%AF%81

https://mp.weixin.qq.com/s/S_n3EwT7oeIgSMCLlJm70w

其实这边直接解压之后再用文件集合导入火眼比较好,不会出现那么多data文件夹,不过我这边还是以普通的直接导入镜像来写好了

1. 分析手机检材,该手机设备名称为

第一题就来个下马威了

看起来火眼这边分析出来的是Xbox Wireless Controller

这明显不是手机设备的名称,因为这是Xbox无线控制器的意思,是手柄的名字

可以看到火眼这边解析的是蓝牙属性,很奇怪,因此这边都不太对

我们需要去找手机设置的路径,可以通过爆搜如device_name等内容找一下

在/data/system/users/0/settings_global.xml我们可以看到真正的设备名称

得到设备名称为REDMAGIC 9 Pro+

比赛的时候没找到这块,做不出去手机相册闲逛了

能看见屏幕截图,正好截图截到了本机的设备名称

2. 分析手机检材,该手机系统magisk【环境版本】为

参考格式是26000,所以明显不该是这边的30.7

搜索magisk

发现了adb中存在记录,过去看看

得到最终答案,环境版本为30600

3. 分析手机检材,嫌疑人通过盖世游戏app安装的《最终幻想》游戏版本是

直接搜索盖世游戏,发现包名为com.xiaoji.egggame

搜索确定路径

进行爆搜,这个路径没有很多文件,所以不管是手动搜索还是爆搜都可以搜到

通过对fantasy进行的搜索,我们最终在data_15/media/0/Android/data/com.xiaoji.egggame/files/Documents/XiaoKunLogcat/XiaoKunLogInfo-5.txt

找到了下的是版本号XIII的最终幻想

4. 分析手机检材,5月6日,嫌疑人最后一次使用谷歌套件中的某个app,其包名是

根据题目说法,将最后使用时间过滤为2026-05-06

再过滤包名含有google

只剩两个了

根据下一题,这个app是会推送新闻的

因此确定是com.google.android.googlequicksearchbox

因为另一个是Googleplay服务,不会推新闻,一般都是推送基础服务,而这一个搜索的更会推新闻一点

如果不放心我们可以由下一题进行验证(虽然比赛的时候根本就没做出第五题

5. 分析上述app5月6日推送新闻的相关痕迹和缓存,新闻《男子拾获钱包以为天降横财》中事件发生的地点是

先根据包名搜索确定路径

题目说了根据痕迹和缓存,明显暗示我们去cache文件夹看看

发现有一个叫做image的文件夹,很显然在这里边

遍历一下就能看到本题答案了

地址是柔佛麻坡

6. 分析该手机关机信息情况,最近一次因电池电池异常过热导致关机的北京时间为

关机信息情况,直接搜索shutdown看看

搜索shutdown发现了一个记录关机的文件夹

里边将关机时间都作为时间戳写入了文件名,只需看内容找电池过热即可

寻找一番,发现是这个文件,里边明确写了

复制代码
BatteryService.lambda$shutdownIfOverTempLocked
Shutdown request from SYSTEM for reason thermal,battery at 2026-01-21 17:28:02.432 GMT+08:00

即电池异常过热

所以答案的时间就是时间戳转化的2026-01-21 17:28:02

或者文件里边其实也写了这个时间,不用转化直接复制粘贴也一样

7. 分析手机检材,北京时间2026-05-06 10:43:38左右那些应用的通知被查看了?

这题真的很难,比赛的时候几乎毫无思路,跟着mumuzi老师走一下

这边需要我们查看使用统计数据库

在/data/system_ce/0/usagestats

这四个是 IntervalStatsObfuscatedProto 文件

即UsageStats应用使用统计文件,文件名的时间戳其实是统计区间的一个开始时间(是一个区间

题目说的时间差不多是1778035418000

所以说左右,最接近的就是第一个文件1778031702128,应该会包含到题目的时间那个区间

接下来就是对里边内容进行了解

这些文件,明文看什么都看看不出,因为他们把很多内容都是保存成了数字token,而映射表是mappings文件

就在同路径下

因此我们可以写脚本读取,整合为一个csv文件,把内容全部提取

复制代码
#!/usr/bin/env python3
from pathlib import Path
from datetime import datetime, timezone, timedelta
import argparse, csv, sys

EVENT_TYPES = {
    0:'NONE',1:'ACTIVITY_RESUMED',2:'ACTIVITY_PAUSED',3:'END_OF_DAY',4:'CONTINUE_PREVIOUS_DAY',
    5:'CONFIGURATION_CHANGE',6:'SYSTEM_INTERACTION',7:'USER_INTERACTION',8:'SHORTCUT_INVOCATION',
    9:'CHOOSER_ACTION',10:'NOTIFICATION_SEEN',11:'STANDBY_BUCKET_CHANGED',12:'NOTIFICATION_INTERRUPTION',
    13:'SLICE_PINNED_PRIV',14:'SLICE_PINNED',15:'SCREEN_INTERACTIVE',16:'SCREEN_NON_INTERACTIVE',
    17:'KEYGUARD_SHOWN',18:'KEYGUARD_HIDDEN',19:'FOREGROUND_SERVICE_START',20:'FOREGROUND_SERVICE_STOP',
    21:'CONTINUING_FOREGROUND_SERVICE',22:'ROLLOVER_FOREGROUND_SERVICE',23:'ACTIVITY_STOPPED',24:'ACTIVITY_DESTROYED',
    25:'FLUSH_TO_DISK',26:'DEVICE_SHUTDOWN',27:'DEVICE_STARTUP',28:'USER_UNLOCKED',29:'USER_STOPPED',
    30:'LOCUS_ID_SET',31:'APP_COMPONENT_USED',
}

def read_varint(data, i):
    shift=0; result=0
    while True:
        if i>=len(data):
            raise EOFError('truncated varint')
        b=data[i]; i+=1
        result |= (b & 0x7f) << shift
        if not (b & 0x80):
            return result, i
        shift += 7
        if shift > 70:
            raise ValueError('varint too long')

def skip_value(data, i, wire):
    if wire == 0:
        _, i = read_varint(data, i)
        return i
    if wire == 1:
        return i+8
    if wire == 2:
        n, i = read_varint(data, i)
        return i+n
    if wire == 5:
        return i+4
    raise ValueError(f'unsupported wire type {wire}')

def iter_fields(data):
    i=0
    while i < len(data):
        key, i = read_varint(data, i)
        field = key >> 3
        wire = key & 7
        val_start = i
        if wire == 0:
            value, i = read_varint(data, i)
            yield field, wire, value
        elif wire == 1:
            value = data[i:i+8]; i += 8; yield field, wire, value
        elif wire == 2:
            n, i = read_varint(data, i)
            value = data[i:i+n]; i += n; yield field, wire, value
        elif wire == 5:
            value = data[i:i+4]; i += 4; yield field, wire, value
        else:
            raise ValueError(f'bad wire {wire} at {val_start}')

def parse_event(msg):
    e={}
    for f,w,v in iter_fields(msg):
        if w != 0:
            continue
        if f == 1: e['package_token'] = v
        elif f == 2: e['class_token'] = v
        elif f == 3: e['time_ms'] = v
        elif f == 4: e['flags'] = v
        elif f == 5: e['type'] = v
        elif f == 7: e['shortcut_id_token'] = v
        elif f == 8: e['standby_bucket'] = v
        elif f == 9: e['notification_channel_id_token'] = v
        elif f == 10: e['instance_id'] = v
        elif f == 11: e['task_root_package_token'] = v
        elif f == 12: e['task_root_class_token'] = v
        elif f == 13: e['locus_id_token'] = v
    return e

def parse_interval(path):
    data=Path(path).read_bytes()
    out={'events':[], 'end_time_ms':None, 'major_version':None, 'minor_version':None, 'packages':0, 'configurations':0}
    for f,w,v in iter_fields(data):
        if f == 1 and w == 0: out['end_time_ms'] = v
        elif f == 2 and w == 0: out['major_version'] = v
        elif f == 3 and w == 0: out['minor_version'] = v
        elif f == 20 and w == 2: out['packages'] += 1
        elif f == 21 and w == 2: out['configurations'] += 1
        elif f == 22 and w == 2: out['events'].append(parse_event(v))
    return out

def parse_mappings(path):
    """Return {package_token: [package_name, string1, string2, ...]}"""
    if not path:
        return {}
    p=Path(path)
    if not p.exists():
        return {}
    data=p.read_bytes()
    maps={}
    for f,w,v in iter_fields(data):
        if f == 2 and w == 2:
            pkg_token=None; strings=[]
            for sf,sw,sv in iter_fields(v):
                if sf == 1 and sw == 0:
                    pkg_token=sv
                elif sf == 2 and sw == 2:
                    try:
                        strings.append(sv.decode('utf-8','replace'))
                    except Exception:
                        strings.append(repr(sv))
            if pkg_token is not None:
                maps[pkg_token]=strings
    return maps

def ts(ms, tz_hours=8):
    return datetime.fromtimestamp(ms/1000, tz=timezone(timedelta(hours=tz_hours))).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]

def token_name(maps, package_token, string_token=None):
    arr = maps.get(package_token)
    if not arr:
        return f'token:{package_token}' if package_token is not None else ''
    if string_token is None:
        return arr[0] if arr else f'token:{package_token}'
    if 0 <= string_token < len(arr):
        return arr[string_token]
    return f'class_token:{string_token}'

def main():
    ap=argparse.ArgumentParser(description='Parse Android /data/system/usagestats/0/daily/* protobuf files')
    ap.add_argument('files', nargs='+', help='daily/weekly/monthly/yearly usage stats files')
    ap.add_argument('-m','--mappings', help='optional /data/system/usagestats/0/mappings file')
    ap.add_argument('--tz', type=int, default=8, help='timezone offset hours, default +8 Beijing')
    ap.add_argument('--csv', dest='csv_path', help='write full event list to CSV')
    ap.add_argument('--only-shutdown', action='store_true', help='print only DEVICE_SHUTDOWN/DEVICE_STARTUP')
    args=ap.parse_args()
    maps=parse_mappings(args.mappings)
    rows=[]
    for fp in args.files:
        p=Path(fp)
        begin_ms=int(p.name.split('.')[0])
        st=parse_interval(p)
        end_abs=begin_ms + (st['end_time_ms'] or 0)
        print(f'\n== {p.name} ==')
        print(f'begin: {ts(begin_ms,args.tz)}  end: {ts(end_abs,args.tz)}  events: {len(st["events"])}  packages: {st["packages"]}  configs: {st["configurations"]}  version: {st["major_version"]}.{st["minor_version"]}')
        for idx,e in enumerate(st['events']):
            typ=e.get('type')
            if args.only_shutdown and typ not in (26,27):
                continue
            abs_ms = begin_ms + e.get('time_ms',0)
            pkg_token=e.get('package_token')
            cls_token=e.get('class_token')
            pkg = token_name(maps, pkg_token) if pkg_token is not None else ''
            cls = token_name(maps, pkg_token, cls_token) if (pkg_token is not None and cls_token is not None) else ''
            row = {
                'file': p.name, 'index': idx, 'time': ts(abs_ms,args.tz), 'abs_ms': abs_ms,
                'type': typ, 'event': EVENT_TYPES.get(typ, f'UNKNOWN_{typ}'),
                'package_token': pkg_token, 'package': pkg,
                'class_token': cls_token, 'class': cls,
                'flags': e.get('flags'), 'instance_id': e.get('instance_id'),
                'task_root_package_token': e.get('task_root_package_token'),
                'task_root_class_token': e.get('task_root_class_token'),
            }
            rows.append(row)
            print(f"{row['time']}  {row['event']:<28} pkg={pkg} cls={cls} raw_pkg={pkg_token} raw_cls={cls_token}")
    if args.csv_path:
        keys=['file','index','time','abs_ms','type','event','package_token','package','class_token','class','flags','instance_id','task_root_package_token','task_root_class_token']
        with open(args.csv_path,'w',newline='',encoding='utf-8-sig') as f:
            w=csv.DictWriter(f, fieldnames=keys); w.writeheader(); w.writerows(rows)
        print(f'\nCSV written: {args.csv_path}')
if __name__ == '__main__':
    main()

整理好之后对event进行筛查,我们只需要看通知被查看的情况,这边选择NOTIFICATION_SEEN

过滤后再次过滤时间

最后得到选AC

8. 该手机曾进行过一次备份,使用的工具是

在data路径发现了一个backup文件夹

里边存在有一个包名的文件夹com.stevesoltys.seedvault

得到包名和路径

所以工具是seedvault

9. 使用上述工具进行备份的具体日期是

上一题已经找到了备份的工具包名

步进查看文件

可以看到上一次lastBackup是1778307867843

转时间即可

所以是2026/5/9

10. 手机重启过程中,屏幕上除厂商logo外,还可以看到以下哪些内容

A. 二维码

B. 密码相关提示

C. FIC

D. 助记词

Android的开机动画文件一般就叫bootanimation.zip

直接搜索即可

打开就可以看到开机动画了

重点就是上边有一个密码1-81-8

还有一个二维码

别的都没有所以答案是AB

11. 分析上述除logo外的信息,发现与加密后的助记词有关,这里用来加密助记词使用的对称加密算法为

除logo以外就一个密码,一个二维码

扫描二维码得到

复制代码
+XTcxmYcgCkSTMTeURBAIOqg7Bz+xq8qlFIzY6SdJ0wl+RSh3g7VvrJRiG9/LwEaKc7I4bTjWzU51wQAwUSNOA==

明显先是一个base64,后边就需要密码了

所以明显现在有一个密文,一个密码,看看能不能解密

说真的,这真的能想到吗()

你需要想到这个1-81-1是两个1到8的意思,也就是1234567812345678

没有找到提示,只能猜测几个需要密码的简单的对称加密算法

最后确定是SM4算法

得到dept aci clus reco jou hors rooki sign san canc will bag

12. 助记词有部分残缺,需要补全的助记词数量为

明显我们刚刚拿到的这个助记词是残缺的,很多单词都不完整

这边最好是有一个BIP39的2048词表,因为Seedvault的12词是基于BIP39的2048词表的,用mnemonic也行

复制代码
https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt

写一个脚本对照,或者一个个对照都可以

复制代码
from mnemonic import Mnemonic

raw = "dept aci clus reco jou hors rooki sign san canc will bag".split()

mnemo = Mnemonic("english")
wordlist = mnemo.wordlist

fixed = []

for x in raw:
    matches = [w for w in wordlist if w.startswith(x)]

    if len(matches) == 1:
        fixed.append(matches[0])
        print(f"{x:8s} -> {matches[0]}")
    elif x in wordlist:
        fixed.append(x)
        print(f"{x:8s} -> {x}")
    else:
        print(f"{x:8s} -> 候选异常: {matches}")
        raise SystemExit

phrase = " ".join(fixed)

print("\n修复后助记词:")
print(phrase)

print("\nBIP39 校验:", mnemo.check(phrase))

所以最后是9个残缺,答案是9

13. 嫌疑人近期使用了一款笔记软件,该应用数据加密使用的主要加密算法为

先锁定软件,基本上可以确定是这个Standard Notes

要看加密算法,那就不能急着仿真,先根据包名去找路径

找到存储的数据库,即可根据这个来研究加密算法了

这边其实很明显开头写了004,Standard Notes的004加密协议就是 XChaCha20+Poly1305

可是比赛的时候哪里知道这是什么协议(

或者就只能看nonce是48位十六进制,即24字节,itemskey是64位十六进制,即32字节,所以得到XChaCha20+Poly1305算法

14. 分析该笔记软件,其数据库一次解密密钥为

写在数据库了已经,这个SN|ItemsKey就是

复制代码
"content_type": "SN|ItemsKey",
"itemsKey": "69bf3693d45cd5485cc53cd7ad9c5c5bf769aa48847253c3991ef18bd3f2ae87",
"version": "004",
"uuid": "6ff1c1bc-3c6d-4003-84e5-e94f4a1f0214"

这个 itemsKey 就是数据库里用于解开各条笔记 enc_item_key 的一次解密密钥

所以一次解密密钥是69bf3693d45cd5485cc53cd7ad9c5c5bf769aa48847253c3991ef18bd3f2ae87

15. 用来解密5月8日的收入的二次解密密钥为

说是5月8日,因此先确定时间

发现对应的应该是 Item-2963e8b4-00b3-4d42-847e-71b2ca79e182

我们知道了是XChaCha20+Poly1305算法,解密即可

复制代码
import base64
import json
from nacl.bindings import crypto_aead_xchacha20poly1305_ietf_decrypt


ITEMS_KEY_HEX = "69bf3693d45cd5485cc53cd7ad9c5c5bf769aa48847253c3991ef18bd3f2ae87"

ENC_ITEM_KEY = "004:7c1ef6f717d20faf8cdca32cef2dd39736704940918302f6:0Pul8qMWK7Gu+12Y/+0qI6QhiLIm4lFrpd7ebnEslEVukAfQ3k/QZqeyHCx3wR2iG+r6urZ4KsS/7W5utnRWfW+HNThEQjtgmLlqIF56mXs=:eyJ1IjoiMjk2M2U4YjQtMDBiMy00ZDQyLTg0N2UtNzFiMmNhNzllMTgyIiwidiI6IjAwNCJ9:e30="

CONTENT = "004:b863f59a4aa43af996eac1293d983a39e104ca83781300b3:NleNZvh1dmlhnnkGu3nnTM4G4LN5nVTSqa8s7RJ/LImFvNUjeOL7nGr3m41ECs5pic+ZhKuhTCggvuQltq9+b9+Y0+aXO79o4to4xda38aYjd5OHu/4PEdpmEqLNS5atTufwORccVjxabns8WRkCHqWeFMBskxniK3xh/rFdyW4e2Md8B7bd/oMuluS3uBR4I+JlkVE2Q0zNqto2sOMHEnnxB1xhtYrvgRi39UMt/oRwIBaTBv/l/qU99P92ESfVdcxfi5AhROTv3aITNh6NOiO+fZ2Z9yhfe5MApBcVATI9sApoBVFd3gXAATqKc043/+WQMVl+fD6D20CXnqERuDX4XP4VEAat2WPLiuiIv73mai9/wxGht2n0kkcK6kljh+6Hx4AH1rEBw6a1WfWr/g==:eyJ1IjoiMjk2M2U4YjQtMDBiMy00ZDQyLTg0N2UtNzFiMmNhNzllMTgyIiwidiI6IjAwNCJ9:e30="


def decrypt_004(payload, key):
    parts = payload.strip().split(":")

    version = parts[0]
    nonce_hex = parts[1]
    ciphertext_b64 = parts[2]
    associated_data_b64 = parts[3]

    if version != "004":
        raise ValueError("不是 Standard Notes 004 格式")

    nonce = bytes.fromhex(nonce_hex)
    ciphertext = base64.b64decode(ciphertext_b64)

    # 这里不能写成 base64.b64decode(parts[3])
    # Standard Notes 004 的 AAD 用的是 base64 字符串本身
    associated_data = associated_data_b64.encode()

    return crypto_aead_xchacha20poly1305_ietf_decrypt(
        ciphertext,
        associated_data,
        nonce,
        key,
    )


def main():
    items_key = bytes.fromhex(ITEMS_KEY_HEX)

    item_key_hex = decrypt_004(ENC_ITEM_KEY, items_key).decode()
    print("[+] 二次解密密钥:")
    print(item_key_hex)

    item_key = bytes.fromhex(item_key_hex)

    content_plain = decrypt_004(CONTENT, item_key).decode("utf-8", "replace")
    print("\n[+] 明文 JSON:")
    print(content_plain)

    content = json.loads(content_plain)
    print("\n[+] 标题:", content.get("title", ""))
    print("[+] 正文:", content.get("text", ""))
    print("[+] 预览:", content.get("preview_plain", ""))


if __name__ == "__main__":
    main()

得到结果

复制代码
[+] 二次解密密钥:
c1790efd9f93361ed78291524d46983d32f866308c4eda7d2228bcd35405e999

[+] 明文 JSON:
{"text":"今日收入59275.25","title":"2026年5月8日星期五 at 13:19","noteType":"plain-text","editorIdentifier":"com.standardnotes.plain-text","references":[],"appData":{"org.standardnotes.sn":{"client_updated_at":"2026-05-08T05:20:05.084Z"}},"preview_plain":"今日收入59275.25"}

[+] 标题: 2026年5月8日星期五 at 13:19
[+] 正文: 今日收入59275.25
[+] 预览: 今日收入59275.25

所以二次解密密钥为c1790efd9f93361ed78291524d46983d32f866308c4eda7d2228bcd35405e999

16. 笔记软件中记录5月7日的收入为

我们可以直接仿真来看

雷电安装之后直接导入应用数据就好了,就不细说了

很方便就能得到5.7收入为84826.90

17. 笔记软件中记录5月6日的收入为

在笔记软件转了好几圈,垃圾桶里也翻过了就是没有5.6的收入,数据库里都没写

实在是难,因为这一题需要我们联系前边的备份来解密,我们需要恢复原来的笔记软件,找到旧版的数据库再进行解密

我们回到之前创建了备份的seedvault

这边打开来可以看到,很像是一个IOS的备份

这边比赛实在是想不明白要怎么做,恢复这备份最方便的就是使用下边这个工具,Seednaut

https://github.com/Baltram/seednaut

我们直接下载使用恢复备份即可

这边需要写一个mnemoic phrase,即我们刚刚恢复的助记词

复制代码
depth acid cluster record journey horse rookie sign sand cancel will bag

发现有两个,我们这边提取6号那个

直接提取即可

然后到老目录下看,找到数据包

里边有个压缩包,提取解压

找到了新的数据库

根据之前的思路解密即可

复制代码
import base64
import json
from nacl.bindings import crypto_aead_xchacha20poly1305_ietf_decrypt


ITEMS_KEY_HEX = "69bf3693d45cd5485cc53cd7ad9c5c5bf769aa48847253c3991ef18bd3f2ae87"

ENC_ITEM_KEY = "004:68d91e5d73ca45af30662758d6d51b22757469e31a5541c8:ORPnUGEq5yG+kzhUVwubXe8PLNrV2a/NJl+sZVyzPrtbtAgnYFC3II/pWro0RwoGKrXWDVT/duXmiSsZREOlL0uP5KxNLGZoZvYvO3obsS0=:eyJ1IjoiNTdiODNjZDMtZDIzNS00OWQ4LWI1ZDAtNWZhOWQ3MmNiYzgzIiwidiI6IjAwNCJ9:e30="

CONTENT = "004:0caebe72961434120849269874d5d0bbdc1160ca4dd35c8f:mH20bxINSWKGE33TwiZPq0dqNtemjukYPZfrUJrrcVDQHa6iOVbnRq3ZAJxgYJpEOdBh9uNr/6PL38KViJh0wHsCJ4/ieHED+iYv734uK0rpryhdaP7TZB5ZYZDKJ75SOp+UCZiY++R9I9RP79V9xtksGnNyOdGy/q3NPQXbmZynvdxO1TJFGhl8QDWbzwYsFmcWGX5/reeOdcbuK9rlddg8kdsfkoTIxX9Z7QJ8xkXwIQa242pGsQYZ4s12WknxpW36UKWQAa/DvstCTRC6ddRMq6HID/96JLtCx5fMv0cTxtCaZMhEL5pSxXK7Zw1SNcQ+hKfDOPC4clOYGnhfTjEoR0JyOpkzGuuIr+Z3wvfHv6HO9BOc64NEBP7Qvr7B8BhONhdPgzxz2vqwnk/W4OtTAqW4NA==:eyJ1IjoiNTdiODNjZDMtZDIzNS00OWQ4LWI1ZDAtNWZhOWQ3MmNiYzgzIiwidiI6IjAwNCJ9:e30="


def decrypt_004(payload, key):
    parts = payload.strip().split(":")

    version = parts[0]
    nonce_hex = parts[1]
    ciphertext_b64 = parts[2]
    associated_data_b64 = parts[3]

    if version != "004":
        raise ValueError("不是 Standard Notes 004 格式")

    nonce = bytes.fromhex(nonce_hex)
    ciphertext = base64.b64decode(ciphertext_b64)

    # 这里不能写成 base64.b64decode(parts[3])
    # Standard Notes 004 的 AAD 用的是 base64 字符串本身
    associated_data = associated_data_b64.encode()

    return crypto_aead_xchacha20poly1305_ietf_decrypt(
        ciphertext,
        associated_data,
        nonce,
        key,
    )


def main():
    items_key = bytes.fromhex(ITEMS_KEY_HEX)

    item_key_hex = decrypt_004(ENC_ITEM_KEY, items_key).decode()
    print("[+] 二次解密密钥:")
    print(item_key_hex)

    item_key = bytes.fromhex(item_key_hex)

    content_plain = decrypt_004(CONTENT, item_key).decode("utf-8", "replace")
    print("\n[+] 明文 JSON:")
    print(content_plain)

    content = json.loads(content_plain)
    print("\n[+] 标题:", content.get("title", ""))
    print("[+] 正文:", content.get("text", ""))
    print("[+] 预览:", content.get("preview_plain", ""))


if __name__ == "__main__":
    main()

所以5.6收入为76583.87

18. 手机检材中有一个AI助手程序,分析该程序配置与任务,哪个应用程序运行后会清空本地存储内容?

ai软件还是比较好锁定的,就是这一个Operit AI

与此同时我们可以在下载处发现一个Operit文件夹

workflow下看见了这样子一个json文件

可以看到访问了com.tencent.mm之后,AI助手会执行/sdcard的动作

即删除本地共享存储目录

所以本题答案是com.tencent.mm

19. 分析AI助手程序调用的模型,结合笔记软件中的记录,用来隐藏银行卡密码的模型文件名为

依旧直接仿真APK,来导入应用数据

直接就能看见,隐藏银行卡密码的模型文件名为ultraman-663M-BF16.gguf

(其实截图里边也有

20. 嫌疑人曾自行修改上题中的模型,他是通过什么原始模型修改而来的?

先定位上题的模型ultraman-663M-BF16.gguf

直接放到Winhex里可以看到是hunyuan模型

所以直接搜索Hunyuan即可搜到模型全称

答案是Hunyuan-7B-Instruct-MNN

21. 被修改后的模型量化精度为

首先是模型里直接写了是BF16

其次这个会写在十六进制,我们只需要搜索general.file_type即可

在 GGUF / llama.cpp 的 general.file_type 枚举里这一个32就是MOSTLY_BF16的意思

所以答案是BF16

装LM Studio里还会自动解析

22. 能够提升修改后模型对话能力的虚拟token为

作为对话,我们可以搜索 chat_template 来定位

chat_template是ai把一段聊天记录转化为模型能读懂的输入格式的地方

所以如果有能提升修改后模型对话能力的虚拟token的话,大概率在这边

发现多了很多,其中每段助手恢复后的结束符都是<|hy_place▁holder▁no▁8|>,这会使其及时停下,提升对话能力

所以答案是<|hy_place▁holder▁no▁8|>

当然也可以去搜一下operit.log

在日志中查找一下对话记录

同样能找到,并发现是每句话末尾必带的token

即为答案

23. 分析AI助手与笔记软件中的记录,找出银行卡6位数字密码为

这边让我们分析AI助手和笔记软件的记录

我们其实都仿真了

笔记里说密码就是秘密

然后AI一直在说秘密的英文是secret

所以直接看这些几乎没有头绪

但是题目给了提示,19题说模型是用来隐藏银行卡密码的

所以也就是说密码一定和模型有关,要么直接写在模型里边,要么对话可以得到密码

首先是对模型进行分析

因为GGUF文件前边的固定结构

复制代码
GGUF 魔数
version
tensor_count
metadata_count
metadata 数据

每一条metadata大概都是

复制代码
key 长度 + key 字符串 + value 类型 + value 内容

所以我们可以以此写脚本,在metadata中列出所有的6位数字

复制代码
import re
import struct
import sys
from pathlib import Path


def read(fmt, f):
    size = struct.calcsize(fmt)
    data = f.read(size)
    if len(data) != size:
        raise EOFError("文件不完整")
    return struct.unpack(fmt, data)[0]


def read_string(f):
    length = read("<Q", f)
    data = f.read(length)
    return data.decode("utf-8", errors="replace")


def read_value(f, value_type):
    # GGUF 常见 value_type
    if value_type == 0:
        return read("<B", f)
    if value_type == 1:
        return read("<b", f)
    if value_type == 2:
        return read("<H", f)
    if value_type == 3:
        return read("<h", f)
    if value_type == 4:
        return read("<I", f)
    if value_type == 5:
        return read("<i", f)
    if value_type == 6:
        return read("<f", f)
    if value_type == 7:
        return bool(read("<?", f))
    if value_type == 8:
        return read_string(f)
    if value_type == 10:
        return read("<Q", f)
    if value_type == 11:
        return read("<q", f)
    if value_type == 12:
        return read("<d", f)

    # ARRAY
    if value_type == 9:
        elem_type = read("<I", f)
        count = read("<Q", f)
        return [read_value(f, elem_type) for _ in range(count)]

    raise ValueError(f"不支持的类型: {value_type}")


def to_hex(s):
    return " ".join(f"{b:02X}" for b in s.encode("utf-8"))


def find_six_digits(key, value):
    results = []

    if isinstance(value, str):
        for m in re.finditer(r"(?<!\d)\d{6}(?!\d)", value):
            num = m.group()
            results.append((key, num, to_hex(num), value))

    elif isinstance(value, int):
        if 100000 <= value <= 999999:
            num = str(value)
            results.append((key, num, to_hex(num), value))

    elif isinstance(value, list):
        for i, item in enumerate(value):
            sub_key = f"{key}[{i}]"
            results.extend(find_six_digits(sub_key, item))

    return results


def parse_gguf(path):
    results = []

    with open(path, "rb") as f:
        magic = f.read(4)
        if magic != b"GGUF":
            raise ValueError("这不是 GGUF 文件")

        version = read("<I", f)
        tensor_count = read("<Q", f)
        metadata_count = read("<Q", f)

        print("[+] version:", version)
        print("[+] tensor_count:", tensor_count)
        print("[+] metadata_count:", metadata_count)

        for _ in range(metadata_count):
            key = read_string(f)
            value_type = read("<I", f)
            value = read_value(f, value_type)

            results.extend(find_six_digits(key, value))

    return results


def main():
    if len(sys.argv) != 2:
        print("用法: python find_6digits.py model.gguf")
        return

    path = Path(sys.argv[1])
    results = parse_gguf(path)

    print("\n========== 六位数字搜索结果 ==========")

    if not results:
        print("[-] 没找到六位数字")
        return

    for key, num, hex_value, source in results:
        print("\n[+] 找到六位数字")
        print("metadata key:", key)
        print("数字:", num)
        print("数字十六进制:", hex_value)

        if isinstance(source, str):
            print("所在内容片段:", source[:200].replace("\n", "\\n"))


if __name__ == "__main__":
    main()

全部列出后发现了一个最可疑的

525252,这就是答案,因为这是tokenizer.ggml.tokens[120818] 里的真实 token 内容 ,即它不是字段数值,也不是 token id,而是token 本身的内容就是 525252

基本上可以确定是故意隐藏的答案,525252

当然我们也可以对ai多拷打拷打

直接装到LM Studio上运行

模型傻了,但是输出了六位数字,所以答案是525252

后边慢慢做()

相关推荐
写做四月一日的四月一日6 小时前
安卓手机安装龙虾openclaw接入deepseek
人工智能·智能手机
wanhengidc11 小时前
服务器数据管理如何
运维·服务器·网络·游戏·智能手机
Tutankaaa13 小时前
知识竞赛移动端适配:手机、平板的界面优化
经验分享·智能手机·电脑
李永奉13 小时前
杰理SDK开发-【BUG】软件开启音量同步连接华为、荣耀手机没有自动开启音量同步
单片机·嵌入式硬件·mcu·物联网·智能手机·bug·语音识别
Raink老师1 天前
【AI面试临阵磨枪-68】设计一个端侧(手机 / 浏览器)轻量化 AI Agent 系统
人工智能·面试·智能手机
Digitally2 天前
4 种方法:将真我手机照片传输到 U 盘
智能手机
私人珍藏库2 天前
【Android】Todesk手机远控手机、电脑,无会员无广告!!
android·学习·智能手机·app·工具·软件·多功能
lauo2 天前
范式革命:从OPPO Reno16到ibbot智体机灵,手机如何从“工具集合”进化为“智慧伙伴”?
智能手机
mianfeixz2 天前
野草助手手机版最新版v2.4.2
智能手机