news 2026/10/10 8:00:26

LLM 驱动的自动化测试系列(07):移动端自动化(三)——Mobile-Agent-v3 与自研 GUI-Owl 模型路线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM 驱动的自动化测试系列(07):移动端自动化(三)——Mobile-Agent-v3 与自研 GUI-Owl 模型路线

Mobile-Agent-v3 是什么,解决什么问题

Mobile-Agent-v3是阿里通义实验室(Tongyi Lab,X-PLUG 团队)开源的跨平台 GUI 智能体框架,核心是自研的GUI-Owl模型——一个专门为 GUI 感知、定位(grounding)、端到端操作训练的多模态模型系列,在 ScreenSpot-v2、ScreenSpot-Pro、OSWorld-G、Android World、OSWorld 等一系列 GUI 自动化基准上号称达到 SOTA。Mobile-Agent-v3 框架本身则是把 GUI-Owl 实例化成多个专职角色(Manager/Executor/ActionReflector/Notetaker),组成一个可以规划、执行、反思、记笔记的多智能体流水线。

跟前两篇 ARTEMIS、Mobilerun 比,最根本的差异不在多智能体分工的形式,而在感知层的哲学:ARTEMIS 和 Mobilerun 都是把截图丢给一个通用对话模型(Gemini/GPT/Claude),指望它顺便把坐标定位也做好;Mobile-Agent-v3 反过来——先训练一个专门做 GUI grounding 的模型,再把这个模型套进多智能体流程里的每个角色。这篇不重复"是什么",直接进入三个问题:它的规划-执行-反思循环具体怎么运作?自研 grounding 模型换来了什么、代价是什么?从论文到开源工具,代码里留下了哪些能直接看到的工程妥协?


四角色流水线:Manager 规划,Executor 执行,ActionReflector 判定成败,Notetaker 记笔记

主控循环在mobile_v3/run_mobileagentv3.py的run_instruction函数里,是一个固定上限的for step in range(max_step)循环(默认max_step=25),每一步内部按顺序调用四个角色:

截图 → 错误检测 → Manager(规划/重规划) → Executor(选动作) → 执行动作 → ActionReflector(判定 A/B/C) → 若成功且开了 Notetaker,记笔记 → 下一轮

四个角色全部继承同一个抽象基类BaseAgent(mobile_v3/utils/mobile_agent_e.py),只有两个方法:

classBaseAgent(ABC):@abstractmethoddefget_prompt(self,info_pool:InfoPool)->str:pass@abstractmethoddefparse_response(self,response:str)->dict:pass

所有角色共享一个InfoPool数据类作为唯一状态载体——没有消息队列,没有独立的 Agent 间通信协议,纯粹是"读同一份状态、往同一份状态写":

@dataclassclassInfoPool:instruction:str=""summary_history:list=field(default_factory=list)action_history:list=field(default_factory=list)action_outcomes:list=field(default_factory=list)error_descriptions:list=field(default_factory=list)important_notes:str=""error_flag_plan:bool=Falseplan:str=""completed_plan:str=""progress_status:str=""err_to_manager_thresh:int=2

这个设计本身很朴素,但正是这种朴素让整套流程可以完整读懂:不存在隐藏在某个消息总线或事件系统里的状态流转,InfoPool的每个字段被谁写、被谁读,直接搜代码就能看到。

Manager(Manager类)负责规划和判断任务是否完成,system 级提示词直接写明角色:“You are an agent who can operate an Android phone on behalf of a user. Your goal is to track progress and devise high-level plans to achieve the user’s requests.” 如果 Manager 认为任务已经完成,会在输出的 Plan 部分标注 “Finished”,主循环检测到这个词就跳出循环。

Executor(Executor类)负责从当前 Plan 的第一个子目标出发,从一份固定的原子动作表里选一个动作执行——不做规划,只做单步动作选择。

ActionReflector(ActionReflector类)是判定上一步动作是否达到预期的角色,输入是执行前后两张截图:

prompt+="The two attached images are phone screenshots taken before and after your last action. \n"...prompt+="A: Successful or Partially Successful. The result of the last action meets the expectation.\n"prompt+="B: Failed. The last action results in a wrong page. I need to return to the previous state.\n"prompt+="C: Failed. The last action produces no changes.\n\n"

这个 A/B/C 三分类是整个容错机制的枢纽——不是简单的成功/失败二分,B(跳到错误页面)和 C(没有任何变化,比如滑动到底了却继续滑)是两种不同的失败模式,对应不同的恢复策略。

Notetaker(Notetaker类)只在动作被判定为 A(成功)且用户传入--notetaker True时才被调用,把当前截图里跟用户目标相关的信息累积进info_pool.important_notes,下一轮 Manager 规划时会把这些笔记读进提示词——这是任务执行过程中积累的"工作记忆",不是训练阶段的知识。


容错阈值:连续两次判定失败才把问题升级给 Manager

这是 Mobile-Agent-v3 容错设计里最值得展开的一处具体机制。主循环每一步开始时都会检查最近err_to_manager_thresh(默认 2)次的action_outcomes:

info_pool.error_flag_plan=Falseerr_to_manager_thresh=info_pool.err_to_manager_threshiflen(info_pool.action_outcomes)>=err_to_manager_thresh:latest_outcomes=info_pool.action_outcomes[-err_to_manager_thresh:]count=0foroutcomeinlatest_outcomes:ifoutcomein["B","C"]:count+=1ifcount==err_to_manager_thresh:info_pool.error_flag_plan=True

只有当最近两次全部是 B 或 C(不能一次 B 一次 A 混着),才会置位error_flag_plan = True。这个标志被置位后,Manager 的下一次规划提示词里会多出一段专门的"潜在卡住"区块:

ifinfo_pool.error_flag_plan:prompt+="### Potentially Stuck! ###\n"prompt+="You have encountered several failed attempts. Here are some logs:\n"k=info_pool.err_to_manager_thresh recent_actions=info_pool.action_history[-k:]recent_summaries=info_pool.summary_history[-k:]recent_err_des=info_pool.error_descriptions[-k:]fori,(act,summ,err_des)inenumerate(zip(recent_actions,recent_summaries,recent_err_des)):prompt+=f"- Attempt: Action:{act}| Description:{summ}| Outcome: Failed | Feedback:{err_des}\n"

值得注意的是,即便置位了这个标志,Manager 拿到的指令也只是"仔细评估当前状态,判断计划是否需要修订"(“Carefully assess the current status… think step by step about whether the overall plan needs to be revised”)——框架本身不强制重新规划,是否要推翻原计划、还是换个动作重试,完全交给模型自己判断。这是一种轻量级的分层升级:Executor 层面允许小的重试噪声(单次失败不惊动 Manager),只有持续失败才把决策权收回到规划层,跟人类操作员"先自己试两次,实在不行再叫上级"的直觉一致。这跟 ARTEMIS 用独立 Checker 节点做后置校验、Mobilerun 用 ActionReflector 式反思是不同量级的设计——Mobile-Agent-v3 的这套阈值机制更朴素,但因为完全暴露在run_mobileagentv3.py的循环体里,是三篇里最容易一次读懂全貌的容错逻辑。

另外主循环里还有一处小的短路优化:如果上一步的动作因为格式解析失败被标记为"invalid",本轮会跳过 Manager 直接重试 Executor——避免一次纯格式错误(不是真的操作失败)也去打扰规划层。


动作空间:六个原子动作,坐标定位不走索引

Mobile-Agent-v3 实际执行时用的动作表比它在new_json_action.py里定义的完整动作常量列表(16+ 项)要窄得多。真正会被 Executor 提示词呈现、也会被主循环执行的,是ATOMIC_ACTION_SIGNITURES_noxml里的六个:

ATOMIC_ACTION_SIGNITURES_noxml={ANSWER:{"arguments":["text"],...},# 回答用户问题CLICK:{"arguments":["coordinate"],...},# 点击 (x, y)LONG_PRESS:{"arguments":["coordinate"],...},# 长按 (x, y)TYPE:{"arguments":["text"],...},# 输入文本SYSTEM_BUTTON:{"arguments":["button"],...},# 系统按键(Back/Home)SWIPE:{"arguments":["coordinate","coordinate2"],...},# 滑动}

对比同为通用 VLM 路线的 Mobilerun(前一篇),Mobilerun 的动作集里既有click(index)(结构化索引点击)也有click_at(x, y)(坐标点击),而且默认屏蔽坐标类工具、只在特定条件下解锁。Mobile-Agent-v3 走的是完全相反的策略——没有索引点击这个选项,坐标点击是唯一的定位方式。这个取舍背后的逻辑很直接:GUI-Owl 是专门训练来做坐标级视觉定位的模型,索引点击依赖的是无障碍树(accessibility tree)这种结构化信息,而结构化信息在真实场景里经常缺失或不可靠(自定义渲染控件、游戏、WebView);既然已经投入训练一个坐标定位能力过硬的模型,就没必要再维护一套结构化索引作为备选路径。这是"自研 grounding 模型"这个选择在动作空间设计上留下的直接痕迹。

坐标的执行落地在主循环里:

ifaction_object['action']=="click":controller.tap(action_object['coordinate'][0],action_object['coordinate'][1])elifaction_object['action']=="swipe":controller.slide(action_object['coordinate'][0],action_object['coordinate'][1],action_object['coordinate2'][0],action_object['coordinate2'][1])

GUI-Owl 输出绝对像素坐标——这是它跟 Qwen-VL、Seed-VL 的关键区别

README 里有一句容易被忽略但信息量很大的话:

“If the model you are using outputs relative coordinates from 0 to 1000, such as Seed-VL or Qwen-VL-2 or Qwen-VL-3, please set--coor_type "qwen-vl"… If the model you are using outputs absolute coordinates. such as Qwen-VL-2.5 or GUI-Owl, please do not set coordinate mapping.”

GUI-Owl默认直接输出跟设备实际分辨率对齐的绝对像素坐标,而不是像 Qwen-VL-2/Seed-VL 那样输出需要归一化到 0-1000 再换算的相对坐标。代码里对应的坐标映射逻辑只在coor_type != "abs"时才会执行:

ifcoor_type!="abs":if"coordinate"inaction_object:action_object['coordinate']=[int(action_object['coordinate'][0]/1000*width),int(action_object['coordinate'][1]/1000*width)]if"coordinate2"inaction_object:action_object['coordinate2']=[int(action_object['coordinate2'][0]/1000*width),int(action_object['coordinate2'][1]/1000*height)]

这里要指出一个真实存在的代码缺陷(不是我的推测,是直接读代码发现的):第一行坐标换算的 Y 分量用的也是width而不是height——coordinate字段的 Y 轴换算写错了,coordinate2那一行反而是对的(用了height)。这个 bug 只在用户显式传入非"abs"的--coor_type(也就是切换到 Qwen-VL 风格的相对坐标模型)时才会触发,用默认的 GUI-Owl 绝对坐标模式完全不会碰到。这恰好印证了"绝对坐标是这个框架的第一等公民,相对坐标兼容路径是后加的、测试覆盖更薄"这个结构性判断。

跟前两篇的直接对比:ARTEMIS 给通用 VLM 配了一个专门的 Gemini Robotics-ER 做坐标定位补强;Mobilerun 完全依赖通用模型自身的视觉理解能力,不做额外投入。Mobile-Agent-v3 是第三条路——不是在通用模型之外加一层定位补强,而是从训练阶段就把"输出可直接用于点击的绝对像素坐标"作为模型能力的一部分。三种路线对应三种不同的成本结构:ARTEMIS 多一次模型调用换精度;Mobilerun 零额外成本但精度完全依赖主模型;Mobile-Agent-v3 把定位精度前置到模型训练阶段,运行时反而是三者里最简单的(一次模型调用,直接拿坐标用)。


平台抽象:一个抽象基类,Android 走 ADB,HarmonyOS 走 HDC——但 HarmonyOS 的输入实现有一个真实的 bug

mobile_v3/utils/controller.py定义了一个极简的抽象基类:

classController(ABC):@abstractmethoddefget_screenshot(self,save_path):pass@abstractmethoddeftap(self,x,y):pass@abstractmethoddeftype(self,text):pass@abstractmethoddefslide(self,x1,y1,x2,y2):pass@abstractmethoddefback(self):pass@abstractmethoddefhome(self):pass

AndroidController全部通过adb shell input ...命令实现;HarmonyOSController全部通过hdc shell uitest uiInput ...命令实现——两套实现方法签名完全一致,调用方(run_mobileagentv3.py)不关心底层是哪个平台,只依赖Controller接口。这是标准的驱动抽象模式,跟前一篇 Mobilerun 的DeviceDriver抽象是同一类工程思路。

但读harmonyos_controller.py的type方法时发现一个真实的实现缺陷:

deftype(self,text):text=text.replace("\\n","_").replace("\n","_")forcharintext:ifchar==' ':command=self.adb_path+f" shell uitest uiInput keyEvent 2050"subprocess.run(command,capture_output=True,text=True,shell=True)elifchar=='_':command=self.hdc_path+f" shell uitest uiInput keyEvent 2054"...

处理空格字符的分支里,command拼接用的是self.adb_path,而HarmonyOSController这个类根本没有adb_path属性,只有__init__里赋值的self.hdc_path。这意味着——只要 HarmonyOS 上要输入的文本里包含空格,这一行就会直接抛AttributeError,其余字符类型的分支都正确使用了self.hdc_path。这不是我的猜测,是直接读代码确认的事实:其余五个方法(tap/slide/back/home/get_screenshot)全部正确使用self.hdc_path,只有type方法里处理空格的这一个分支写错了变量名。这类 bug 通常意味着 HarmonyOS 路径的测试覆盖不如 Android 路径充分——大概率是从 Android 实现复制粘贴改造时漏掉了一处替换,且测试用例里没有覆盖"输入带空格的文本"这个场景。


GUI-Owl 的调用方式:OpenAI 兼容协议,温度锁定为 0

mobile_v3/utils/call_mobile_agent_e.py里的GUIOwlWrapper类把 GUI-Owl 包装成标准的 OpenAI 兼容客户端调用:

classGUIOwlWrapper(LlmWrapper,MultimodalLlmWrapper):RETRY_WAITING_SECONDS=20def__init__(self,api_key,base_url,model_name,max_retry=10,temperature=0.0):self.max_retry=min(max_retry,10)self.temperature=temperature self.model=model_name self.bot=OpenAI(api_key=api_key,base_url=base_url,timeout=30)

temperature参数硬编码默认值为0.0——所有四个角色(Manager/Executor/ActionReflector/Notetaker)共享同一个GUIOwlWrapper实例,也就共享同一份"确定性优先"的采样策略,这跟前一篇 Mobilerun 按角色配不同 temperature(Manager 0.2、Executor 0.1)的精细化配置形成对比——Mobile-Agent-v3 目前的开源版本没有走"角色级差异化采样"这条路,四个角色虽然分工不同,但调用同一个模型、同一套采样参数。

图像预处理调用了qwen_vl_utils.smart_resize(),把截图按最小/最大像素预算(MIN_PIXELS=3136,MAX_PIXELS=10035200)自适应缩放后转 base64——这是 Qwen 系模型family 的标准图像预处理管线,符合 GUI-Owl 基于 Qwen-VL 系脉络训练的背景。


从论文到工具:留在代码里的工程痕迹

逐个文件读下来,mobile_v3/目录里能找到的"未完工"痕迹不多,但确实存在——比如MobileUse.parameters的动作枚举列表里,answer这一项后面直接跟着一行注释# todo:

"enum":["key","click","long_press","swipe","type","answer",# todo"system_button","open","wait","terminate",],

这条注释没有说明具体待办内容是什么,结合下面这个更具体的发现,能看出这个文件本身的完工程度确实不如mobile_v3/里真正驱动执行的那几个文件:

function_call_mobile_answer.py里的工具类是一个纯 schema 定义,没有真正的执行逻辑——这个文件定义了一个MobileUse(BaseTool)类,通过@register_tool("mobile_use")注册进 Qwen-Agent 的工具体系,description和parameters字段完整描述了十个动作类型(包括比主循环实际支持的六个多出来的key/open/wait/terminate)。但call()方法分发到的每一个具体动作方法——_key/_click/_long_press/_swipe/_type/_answer/_system_button/_open/_wait/_terminate——全部是一行raise NotImplementedError(),没有任何一个真正实现:

def_click(self,coordinate:Tuple[int,int]):raiseNotImplementedError()def_type(self,text:str):raiseNotImplementedError()

也就是说这个类调用起来必定抛异常,它的价值仅限于给 Qwen-Agent 框架提供一份description/parametersschema(文件末尾的if __name__ == "__main__"demo 也只是打印NousFnCallPrompt解析出的function_call结构,从未真正调用mobile_use.call()走到这些空方法)。真正的执行逻辑在run_mobileagentv3.py的主循环里单独实现,两者的动作命名和参数结构相似但不是同一套代码路径——这份 schema 更像是给"用 Qwen-Agent 框架接入 GUI-Owl"这种替代集成方式准备的文档化产物,而不是 Mobile-Agent-v3 主流程实际依赖的代码。

Notetaker 提示词里混入了跑评测时留下的任务专属规则,比如:

if"transactions"ininfo_pool.instructionand"Simple Gallery"ininfo_pool.instruction:prompt+="### Guideline ###\nYou can only record the transaction information in DCIM..."

这类针对特定基准测试任务(AndroidWorld 的具体题目)写的硬编码分支,混在通用框架代码里,是学术评测代码常见的痕迹——为了在基准上跑分而加的特判逻辑,没有被清理就直接开源了。这跟 Mobile-Agent-E(Mobile-Agent-v3 的前身项目)保留的"经验短语/提示词"(tips/shortcuts)持久化机制不同:Mobile-Agent-E 有专门的ExperienceRetrieverShortCut/ExperienceRetrieverTips角色,把跨任务的经验持久化存到文件里、下次任务开始前检索复用;Mobile-Agent-v3 的mobile_agent_e.py(尽管文件名沿用了旧项目名)里完全没有tips/shortcuts相关字段——这个跨任务经验持久化机制在 v3 里被整体移除了,Notetaker 现在只做单次任务内的笔记积累,不再跨任务复用。

评测代码被拆到独立目录:os_world_v3/和android_world_v3/分别包含各自的评测脚本(run_guiowl.sh/run_ma3.sh),跟mobile_v3/(真机运行)物理分离——这是合理的工程组织,评测跑分和真机部署本来就该是两套入口。


顺带一提:v3.5 把多角色能力收进了模型本身

仓库里还有一个更新的Mobile-Agent-v3.5目录,值得简单交代一下走向。v3.5 对应的模型是GUI-Owl-1.5,README 的关键描述是:

“Multi-agent ready: Serves both as a standalone end-to-end agent and as specialized roles (planner, executor, verifier, notetaker) within the Mobile-Agent-v3.5 framework.”

但实际去读mobile_use/run_gui_owl_1_5_for_mobile.py(v3.5 目前唯一的移动端运行脚本,287 行)会发现:这个脚本里没有 Manager/Executor/ActionReflector 这些独立角色类,只有一个main()函数里的单一循环——截图、调用一次模型、解析<tool_call>格式的输出、执行动作、记录历史,如此重复。规划、执行、验证这几种能力不再体现为流程里的多个显式角色调用,而是被描述为已经"收敛进模型本身"——即模型在一次推理里就同时完成了它们,框架层面不再需要为每个角色单独调用一次模型。

这跟 v3 形成了直接的对照:v3 是"一个模型,多次调用,扮演不同角色"(四次调用/步),v3.5 的移动端脚本是"一个模型,一次调用,角色内化"(一次调用/步)。这是否代表 Mobile-Agent 系列未来会放弃显式多智能体编排、把复杂度转移到模型训练阶段,目前只能从这一个运行脚本的写法上看出趋势,本地仓库里没有 v3.5 版本的 Manager/Executor 独立编排代码可供对比确认,评测代码(android_world_v3.5/)也需要单独确认是否验证了这一走向——这一点应作为待观察的方向,而不是下定论。


总结

  1. Mobile-Agent-v3 用同一个BaseAgent抽象基类定义四个角色——Manager(规划)、Executor(选动作)、ActionReflector(判定 A/B/C 三态结果)、Notetaker(记笔记),全部读写同一份InfoPool状态,没有独立的 Agent 间通信协议
  2. 容错机制是一个具体、朴素、完全可读的阈值设计:连续两次判定为 B 或 C 才把问题升级给 Manager 重新规划,单次失败允许 Executor 自行重试,不惊动规划层
  3. 动作空间只有六个原子动作(answer/click/long_press/type/system_button/swipe),坐标定位是唯一的定位方式,没有索引点击这个选项——这是自研 GUI-Owl grounding 模型带来的直接设计后果
  4. GUI-Owl 默认输出跟设备分辨率对齐的绝对像素坐标,跟 Qwen-VL-2/Seed-VL 的 0-1000 相对坐标不同;代码里有一处只在切换到相对坐标模式时才会触发的坐标换算 bug(Y 轴误用了 width)
  5. Android/HarmonyOS 共享同一个Controller抽象基类,但 HarmonyOS 的type方法处理空格字符时错误引用了不存在的self.adb_path——这是一个会直接导致AttributeError的真实代码缺陷,指向 HarmonyOS 路径的测试覆盖不足
  6. 三篇里三种不同的定位精度成本结构:ARTEMIS 是"通用模型 + 专用定位模型补强",Mobilerun 是"完全依赖通用模型自身能力",Mobile-Agent-v3 是"训练阶段就把定位精度做进模型本身";仓库里更新的 v3.5 迹象显示,规划/执行/验证这些角色能力也正在被尝试收进模型一次推理内部完成,但这一走向目前只能从一个运行脚本观察到,尚不构成完整证据

欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。

更多实用知识和有趣产品,欢迎访问我的个人主页

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 8:00:15

DeepSeek实战指南:从API调用到本地部署与工具链接入

简介&#xff1a;这是一份DeepSeek AI平台的系统操作手册&#xff0c;从基础准备到高阶玩法共分六大部分&#xff0c;面向初次接触AI工具的新用户、想深度应用AI辅助工作的技术人员&#xff0c;以及需要借助AI进行内容生产与学习管理的人群。资源为单个PDF文件&#xff0c;约1.…

作者头像 李华
网站建设 2026/10/10 7:59:55

AI短视频漫剧制作运营全拆解:从角色一致性到账号冷启动

1. 从“AI短视频漫剧”这个班名里&#xff0c;我读出了什么第一次看到“AI短视频漫剧制作运营班”这个标题&#xff0c;我脑子里蹦出来的不是“又是一个卖课的”&#xff0c;而是三个很具体的问号&#xff1a;漫剧到底是什么形态&#xff1f;AI在里面到底替代了哪个环节&#x…

作者头像 李华
网站建设 2026/10/10 7:59:47

iOS相册多选与删除完整指南:PHPicker与PhotoKit权限避坑

简介&#xff1a;面向iOS开发者的相册图片多选与删除功能实现资料&#xff0c;聚焦如何借助QBImagePickerController完成系统相册多选、删除、拍照及图片压缩等常见需求&#xff0c;适合正在开发社交或图片编辑类应用、需要处理图片选择的初中级iOS工程师。资源包为1个PDF文件&…

作者头像 李华
网站建设 2026/10/10 7:59:46

用Claude Opus 5.5打造代码视频五步流水线

先给你打个预防针&#xff1a;Claude Opus 5.5 并不会像 Sora 那样&#xff0c;给你一句话就吐出一段视频。很多人第一次听到“用 Claude 做视频”就往生成式视频那个方向想&#xff0c;但代码视频这条路完全是另一套逻辑——模型负责写代码&#xff0c;渲染引擎负责画画面&…

作者头像 李华
网站建设 2026/10/10 7:59:14

DeepSeek大模型实战指南:从架构解析到128K上下文部署

1. 这不是“笔记”&#xff0c;而是一份可复现的大模型认知地图DeepSeek大模型学习笔记——看到这个标题&#xff0c;很多人第一反应是&#xff1a;又一份堆砌术语的PPT式总结&#xff1f;或者干脆是某位同学课后随手记的零散想法&#xff1f;但如果你真这么想&#xff0c;就错…

作者头像 李华
网站建设 2026/10/10 7:58:59

CTF MISC文件操作实战:从类型识别到分离合并

简介&#xff1a;面向CTF竞赛初学者及安全爱好者的PDF资料&#xff0c;系统讲解杂项基础解题技能&#xff0c;重点覆盖文件类型识别、分离与合并三大模块。文档从常考题型切入&#xff0c;逐步演示file命令与010Editor判别文件类型&#xff0c;再结合Binwalk、foremost、dd、fc…

作者头像 李华