PDF这玩意儿,平时看着人畜无害,谁天天办公不跟它打交道。但恰恰是这种“天天见”的文件格式,成了病毒木马渗透里最爱用的载体之一。我处理过不少终端中招的案例,溯源到最后,入口往往不是那个刺眼的exe,而是一份看起来很正常的PDF——可能是“发票”、是“简历”、是“产品手册”。今天就把这种“基础的PDF病毒木马”从里到外拆一遍,讲清楚它到底是怎么运行的、恶意代码藏在哪儿、我们怎么在不双击的情况下把它揪出来,以及日常到底该怎么防。这篇文章适合搞安全运维的同行,也适合那些总收到陌生PDF、心里发毛的普通办公用户。
1. PDF文件结构本质:为什么PDF天生就是恶意容器的料
很多人以为PDF就是“电子版的图片”,这么理解就大错特错了。PDF的全称是Portable Document Format,它本质是一个结构化的文档容器,内部由大量对象(Object)组成,每个对象都有自己的类型和编号。攻击者看中它,正是因为它能干的事情远不止“显示文字图片”这么简单。
1.1 PDF对象、动作与脚本:不只是排版语言
一个标准PDF文件里,最基本的组织单位是对象(indirect object)。比如5 0 obj到endobj之间就是一个对象。这些对象构成了页面(Page)、目录(Catalog)、字体(Font)、内容流(Content Stream)等元素。
关键在于,PDF规范里定义了几类特殊的对象,它们直接与“执行行为”挂钩:
/OpenAction:文档打开时自动执行的动作。这是最经典的恶意触发点,PDF阅读器一打开文件,不用你点击任何东西,就会触发这里定义的行为。/AA(Additional Action):附加动作,可以在页面加载、关闭、鼠标事件时触发。攻击者会把恶意代码捆绑在这些事件上。/JavaScript:定义JavaScript脚本对象。PDF的JS引擎能力不算强,但做文件下载、弹窗、发起网络请求、配合漏洞利用是绰绰有余的。/Launch:启动外部程序的动作。新一代PDF阅读器对这个限制很严,会弹窗确认,但旧版本或者某些第三方阅读器可不会跟你客气。/EmbeddedFile:内嵌文件对象。可以嵌入任何二进制数据,包括可执行文件。
我经常用一个类比跟人解释:PDF就像一个“带自动播放功能的文件夹”。你以为你只是打开了一个文件夹看图片,实际上里面的脚本可能已经悄悄运行了。
1.2 为什么攻击者偏爱PDF作为投递载体
从攻击者的视角看,PDF作为恶意代码的第一阶段投递载体,好处非常明显:
- 信任度高。在绝大多数人心里,PDF“只是文档”,不是“程序”。比起收到exe时的警惕,收到PDF时几乎没几个人会犹豫。
- 跨平台通用。Windows、macOS、Linux都有PDF阅读器,一套通用的恶意PDF可以打多个平台,只是利用的漏洞不同而已。
- 解析器差异大。Adobe Acrobat家族、Foxit Reader、Chrome内置查看器、各种在线预览服务……它们的解析逻辑、对脚本的支持程度、漏洞修补速度各不相同。攻击者只要挑一个用户基数大、补丁跟得慢的阅读器下手就行。
- 文件结构高度可混淆。PDF允许对象乱序、交叉引用表缺失、流编码变换,这使得静态检测非常困难,同一个恶意逻辑能变形出无数个hash。
说白了,PDF病毒木马的“基础”两个字,指的不是简陋,而是它在恶意软件投递链里的基础地位——几乎所有APT攻击、勒索软件投放、钓鱼活动,都是从这样一个“不起眼”的PDF开始的。
2. 主流攻击手法拆解:恶意PDF到底是怎么得手的
知道了PDF结构里藏着哪些能“搞事”的对象,接下来看这些对象在实际攻击里是怎么被组合利用的。这章节我会把常见手法拆开揉碎了讲,你在分析样本时可以对号入座。
2.1 JavaScript自动执行:最经典的入口
恶意PDF最常用的一招,就是利用/OpenAction+/JavaScript组合。PDF一旦打开,自动执行JS代码。这段JS能干的事包括但不限于:
- 判断当前阅读器版本和平台,选择对应的漏洞利用代码。
- 释放shellcode到内存,触发内存损坏漏洞。
- 发起HTTP请求,从远程服务器下载第二阶段的恶意载荷。
举个例子,攻击者会在PDF里写一段JavaScript,先通过app.viewerVersion或app.platform获取阅读器版本,再调用this.submitForm()向攻击者指定服务器提交数据,或者拼接特定的恶意URL让app.launchURL()去访问。
很多传统的CVE漏洞利用链,入口都是这个JS。Adobe Acrobat和Reader历史上的许多高危漏洞,比如CVE-2013-2729(内存破坏)、CVE-2016-7855(UAF),都是通过恶意PDF里的JS触发的。
2.2 利用阅读器自身漏洞:从文档到代码执行
JS自动执行只是“启动开关”,真正让恶意PDF从“弹个窗”变成“完全控制你电脑”的,是阅读器软件本身的漏洞。
理解这个逻辑很重要:PDF解析器是一个复杂软件,它解析各种对象、处理各种编码、渲染各种字体。攻击者在对象结构、流编码、字体数据里做手脚,构造出内存破坏条件,就能让解析器在解析PDF时执行攻击者控制的代码。这跟溢出的原理一样,只是入口换成了PDF的某个畸形结构。
这类漏洞利用的典型思路是:
- 前期通过JS堆喷(Heap Spray),在内存中布满攻击者的shellcode。
- 触发某个解析漏洞(如类型混淆、UAF、整数溢出),劫持控制流。
- shellcode执行后,通常会释放一个真正的恶意载荷,比如远控木马、勒索软件。
我经手过的样本里,最常见的几个漏洞组件包括JavaScript引擎、字体解析(尤其是Type1字体)、图像解码器(JPEG2000)这几块,攻击者反复在这些组件里找新的可利用点。
2.3 内嵌文件与Launch动作:明枪易躲、暗箭难防
看了太多复杂漏洞利用,你可能会觉得恶意PDF都很“高端”。其实不然,很多基础攻击根本不打漏洞,直接用/Launch内嵌一个可执行文件,或者把exe藏在/EmbeddedFile里,然后前端伪装成一个“点击此处查看文档内容”的按钮。
这种手法对技术门槛要求极低,但在社会工程学上的成功率并不低。攻击者把恶意exe命名成invoice_2024_0821.pdf.exe,嵌进PDF里,诱导用户点击。用户一看文件图标是PDF(实际是exe改图标),双击后系统运行的是木马,但弹出来的可能是一个打不开的PDF,让用户以为是“文件有问题”而忽略。
注意:基础攻击手法里,这种“低技术社会工程学”的攻击占比非常高,远远超过漏洞利用。很多用户只防exe,不防“PDF里面的exe”,这就是认知盲区。
2.4 钓鱼跳转与表单域:不弹恶意代码的PDF
还有些恶意PDF本身并不带恶意代码,它们只负责“引路”。利用/URI动作或者伪造一个表单域,让文件在被打开时自动跳转到一个钓鱼网站,页面高度仿真微软登录页或网银页面,诱导用户输入账号密码。
这里有个非常狡猾的细节:这类PDF往往是合法工具生成的,没有任何恶意代码特征,杀毒软件大概率不报。它纯粹利用人的信任链——你收到一封“您的发票已开具,详情见附件”的邮件,附件PDF打开后弹出一个浏览器页面,页面长得跟你的邮箱登录页一模一样,一般人很难意识到自己已经被钓走了。
2.5 现代变异:从单一漏洞到多阶段混淆
现在的恶意PDF还有一个明显趋势:不再单独作战。它把PDF当作一个“下载器”或“加载器”,第一阶段只在内存中执行一段很小的代码,然后从远程服务器拉取第二阶段的攻击模块。这样的好处是初始样本的体积小、特征少,杀软扫描时很难命中;就算安全软件把PDF查杀了,C2服务器一换,文件一改,又是新的样本。
我见过的最极端的案例,一个PDF只有十几KB,但里面嵌套了三层编码、两段混淆的JS,最后只做了一件事:从远程拉取一个DLL到本地并加载。整个过程极其隐蔽,不抓网络行为根本看不出来。
3. 静态分析实操:不双击,先把PDF的底裤看穿
遇到可疑PDF,我的建议永远是:先静态,后动态。静态分析就是在不打开PDF、不让代码执行的前提下,把PDF的结构、脚本、对象引用扒出来看。这一步做好了,很多恶意PDF直接就“原形毕露”,根本不需要跑动态流程。
3.1 环境准备与工具清单
工欲善其事,必先利其器。我建议你准备一个干净的Linux分析虚拟机,装上以下几样工具:
pdfid.py:Didier Stevens的作品,快速扫描PDF中可疑对象的关键统计工具,适合第一眼判断。peepdf:交互式PDF分析工具,支持解压缩流、提取JS、分析对象关系,功能非常全面。qpdf:开源PDF处理工具,能对PDF进行线性化、解压对象流等常规手术操作。pdf-parser.py:同样是Didier Stevens的作品,命令行方式解析PDF结构,适合写脚本批量处理。strings、hexdump:Linux自带,做最粗暴的字符串提取和十六进制查看。
3.2 第一步:快速评分判断恶意嫌疑
拿到一个PDF,先用pdfid看看整体结构特征:
python pdfid.py sample.pdf输出大概长这样:
PDFiD 0.2.8 sample.pdf PDF Headers: PDF-1.7 obj 357 endobj 357 stream 215 endstream 215 xref 1 trailer 1 startxref 1 /Page 93 /Encrypt 0 /ObjStm 10 /JS 1 /JavaScript 1 /AA 2 /OpenAction 1 /AcroForm 3 /JBIG2Decode 0 /RichMedia 0 /Launch 1 /EmbeddedFile 2这里面有敏感的/JS、/JavaScript、/OpenAction、/Launch、/EmbeddedFile,那基本可以断定这文件有恶意嫌疑了。正常业务生成的PDF,比如Word导出的,极少会带这些对象。
有一种情况要排除:AcroForm(表单)里合法使用JavaScript做字段计算,这在电子签章、动态表单里蛮常见的。判断时要结合触发方式——只是用户点击表单才触发,和文档一打开就触发,恶意程度完全不同。
3.3 第二步:解压与提取JS代码
有JS嫌疑,就要把脚本内容提出来看。用peepdf可以很方便地做到:
peepdf -f -i sample.pdf进入交互模式后:
# 列出所有对象 list objects # 提取/分析JS对象 extract js也可以直接用pdf-parser精确提取某个对象:
# 分析所有对象,显示对象编号、类型 pdf-parser.py -a sample.pdf # 解码指定编号的对象内容(比如找JS时看到的对象编号29) pdf-parser.py -o 29 -f -d extracted_29.js sample.pdf提取出来的JS代码量通常不会很大,效果好的话直接能看到恶意逻辑。比如:
var shellcode = unescape("%u9090%u9090%uEB0C%u5A8B..."); var heapblock = unescape("%u0D0D%u0D0D..."); while(heapblock.length < 0x20000) heapblock += heapblock; var memory = new Array(); for(i=0;i<1000;i++) memory[i] = heapblock + shellcode;看到这种堆喷代码,就基本确认这是漏洞利用型恶意PDF了,后面直接用动态分析去验证。
3.4 第三步:梳理对象关系与触发链路
静态分析不只是看代码片段,更重要的是梳理“谁触发了谁”。重点关注以下几个关键对象:
- Catalog对象(通常编号为1)里的
/OpenAction引用,看它指向哪个对象。 - 页面对象里的
/AA(附加动作)。 - 所有
/JavaScript对象里是否包含app.launchURL()、this.submitForm()等危险函数。 /Launch对象指向的文件名、参数。/EmbeddedFile对象的类型和大小,看看里面藏的是不是PE文件。
peepdf的tree命令可以直接生成对象关系树,一眼看到Catalog → OpenAction → JavaScript → 恶意代码这条链:
tree3.5 混淆手段识别:静态分析的进阶功课
恶意PDF作者也不傻,他们知道分析师会看JS代码,所以会做各种混淆。基础的包括:
- 十六进制/十进制编码字符串,运行时再解码。
- 字符串分段拼接,比如把
http://evil.com/payload.exe拆成七八段再拼起来。 - 利用数组、字典存储恶意数据,动态索引读取。
- 多层编码的流对象(FlateDecode之后还有一层自定义解码,比如异或运算)。
遇到这种情况,我的建议是不必纯手工硬抠。先把提取出的JS代码格式化,逐行理解,对于混淆严重的,直接在peepdf里动态执行JS一部分逻辑(peepdf支持调用js引擎执行片段),或者将JS代码复制到Node.js里跑一遍看输出。前提是你有可控的分析环境,千万别在自己工作机上乱跑。
4. 动态行为分析:让恶意PDF真正“跑起来”看它想干嘛
静态分析能看到“皮”,但看不全“骨”。特别是面对带反分析逻辑、深度混淆的样本,必须把它丢进沙箱里动态跑一遍,观察文件系统、进程、网络三个层面的行为。这一节讲的是我的动态分析标准流程。
4.1 沙箱环境设计与快照恢复
动态分析的第一原则:永远别在实机或主力虚拟机里直接打开可疑PDF。我的做法是准备一台专用的Windows分析虚拟机,配置如下:
- 系统:Windows 10 LTSC,关闭Windows Defender实时保护(避免干扰)。
- 软件:目标阅读器(如Adobe Acrobat Reader DC特定版本)、Process Monitor、Wireshark或Fiddler。
- 状态:做完所有配置后打一个干净快照。每次分析完直接恢复快照,回到纯净环境。
保持环境纯净很关键,否则之前的分析残留会影响下一次判读,比如网络请求、注册表项、文件落盘这些结果会被污染。
4.2 进程行为监控
用Process Monitor(Procmon)监控PDF进程及其子进程的完整行为:
- 打开Procmon,设置过滤条件:Process Name 包含
AcroRd32.exe或chrome.exe(取决于你用哪个阅读器),Include 这个进程及其子进程。 - 清空日志后,双击打开恶意PDF。
- 观察几秒到几分钟,然后停止捕获。
重点看这几类事件:
CreateFile、WriteFile:PDF进程有没有在临时目录、启动目录、用户目录写入可执行文件或DLL。RegSetValue、RegCreateKey:有没有写入HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run这类持久化启动项。CreateProcess、Process Start:有没有派生子进程,比如powershell.exe、cmd.exe、mshta.exe、rundll32.exe。
恶意PDF最典型的行为链就是:Adobe Reader启动→释放诱饵PDF→子进程powershell.exe→下载并运行第二阶段载荷。Procmon里看到这条链,基本就实锤了。
4.3 文件系统痕迹与网络行为
文件系统方面,除了Procmon,还可以在打开PDF前对关键目录做一次文件列表快照,打开后再做一次,用diff对比找出新增文件:
# 打开PDF前 dir /s /b C:\Users\admin\AppData\Local\Temp > before.txt # 打开PDF后 dir /s /b C:\Users\admin\AppData\Local\Temp > after.txt fc before.txt after.txt网络行为是判断恶意PDF意图的重要维度。用Wireshark抓包时,过滤器设置为:
ip.addr != your_dns_server_ip and ip.addr != your_local_ip或者直接监控DNS查询、HTTP请求。恶意PDF的JS通常会向远程C2服务器发起DNS解析和HTTP通信,请求路径可能是.php、.asp、/images/logo.png这种伪装路径,但实际上返回的是加密数据。
注意:如果在分析时发现PDF向一个陌生海外IP发起加密通信,而且请求特征非常规律(固定时间间隔的心跳),这大概率是C2隧道建立成功了。此时应立即停止继续操作,恢复快照,并把IOC(IP、域名、URL)记录下来。
4.4 无GUI环境的容器化动态分析
如果样本量很大,一台虚拟机一个一个点效率太低。我一般会辅以Linux下的容器化分析环境。用Firejail跑一个受限的PDF阅读器(比如firejail --net=none evince sample.pdf),然后配合strace看系统调用、监控文件访问,这样虽然还原不了Windows环境的恶意行为,但对研究沙箱逃逸、了解样本的外部行为是够用的。
firejail --net=none strace -f -e trace=file,process,network -o trace.log evince sample.pdf这个方案最大的优势是快、干净、无污染,适合大批量初筛。一旦在Linux环境里观察到PDF调用了异常系统调用、尝试访问网络或执行其它程序,这个样本就直接升级标记为高危,再转入Windows虚拟机做深度动态分析。
4.5 动态分析中的反沙箱对抗
高级样本会做反虚拟化检测,比如:
- 检测当前进程名,如果发现
procmon.exe、wireshark.exe名称,直接不执行恶意逻辑。 - 检测系统时间,如果与实际日期相差太大(虚拟机快照回滚导致),停止运行。
- 检测是否有用户交互,长时间无人操作就休眠。
- 检测CPU核心数、内存大小,不符合常规配置就退出。
应对办法也很朴素:Procmon改名或干脆用轻量级的监控工具,分析时模拟人工操作(移动鼠标、点击页面),给虚拟机配置合理的硬件资源。道高一尺,魔高一丈,分析本质就是一场对抗。
5. 企业环境与个人终端的防御实践
分析完恶意PDF的行为模式,最后落到日常防御上。不管是企业安全运维还是个人用户,都有可落地的防护方案。
5.1 阅读器侧基线配置
如果组织里必须使用Adobe Acrobat Reader DC,安全配置应该做到以下几点:
- 开启“受保护的视图”(沙箱模式),所有来自互联网的PDF都在受限环境中打开。
- 禁用JavaScript。位置在:编辑 → 首选项 → JavaScript → 取消勾选“启用Acrobat JavaScript”。对绝大多数办公场景,禁用JS不影响正常阅读,但能直接封死一大半漏洞利用入口。
- 关闭“启动时打开附件”、“允许文档级别的特权”等选项。
- 将公司内部可信来源加入“特权位置”列表,除此之外的PDF一律走沙箱。
注意:很多人觉得禁用JS会影响PDF表单功能,但在内网办公场景,表单通常用专门的电子签章系统,而非阅读器内置JS。为了安全牺牲这点便利,非常值得。
5.2 网关侧的过滤与检测
从企业防线看,邮件网关是第一道关卡。建议在邮件网关处增加以下策略:
- 压缩包套PDF的邮件直接提升风险等级,特别是
zip、rar里套pdf再套exe的组合。 - PDF附件内嵌可执行文件、OpenAction动作的,直接阻断或隔离。
- 对入站邮件做沙箱检测,让可疑附件在沙箱里打开看行为,而不是直接放行给终端。
现在的商业邮件安全网关大都支持附件沙箱,关键是策略要开、要有专人跟进告警。很多企业买了功能不用,纯当摆设,跟没买没区别。
5.3 个人用户习惯:不把PDF当“安全文件”
个人层面,几个小习惯能帮你规避90%的套路:
- 陌生来源的PDF,先上传到VirusTotal或微步云沙箱扫描,确认干净再打开。别嫌麻烦,出事了更麻烦。
- 优先使用浏览器内置PDF查看器预览。Chrome、Edge的内置PDF查看器是沙箱化的,即使PDF带恶意代码,也不容易影响整个系统。
- 不要在PDF阅读器里点击任何“下载插件”“升级播放器”之类的提示。这是社交工程重灾区。
- 如果电脑里有重要数据,始终保证系统、阅读器、杀毒软件处于最新状态。很多恶意PDF利用的漏洞是几年前的,补丁打全了根本打不进来。
5.4 与其他恶意载荷的“组合拳”防御
别忘了PDF常常只是第一步。它下载的可能是DLL、是PowerShell脚本、是宏文档。所以在终端侧,除了管好PDF阅读器,还得把宏禁用、PowerShell执行策略、应用白名单等基础安全配置一起做了。我见过太多案例:PDF的入口防住了,结果攻击者改用邮件附件里的XLSM宏打进来,一样沦陷。防御必须是一条完整的链,不能有短板。
6. 实战中的常见误判与分析陷阱
写了这么多识别方法,最后说说反面的东西——我自己踩过和见过别人踩的坑。
6.1 正常的自动化PDF也会带JS
现在很多企业用系统自动生成PDF报表,比如BI系统导出的图表、电子发票、银行电子回单。这类PDF为了保证表单字段自动计算,会内置合法的JavaScript,也会带上AcroForm对象。单看/JavaScript特征会误报。
怎么区分?结合触发时机和代码复杂度。正常PDF的JS通常是简单的字段赋值、格式化函数,代码量很小、逻辑清晰;恶意PDF的JS往往有编码混淆、堆喷、版本检测、shellcode填充等高危特征。看一眼代码内容,比只看统计标记靠谱得多。
6.2 扫描件“套壳”PDF的盲区
还有一种容易混淆的情况:攻击者把恶意代码藏在一个扫描件PDF里——即先用扫描仪生成一份干净的图片PDF,再通过PDF编辑工具往里面嵌入恶意对象。表面看这个PDF就是一个图片流,视觉效果跟正常扫描件一模一样,但对象表里可能藏着额外的JS或EmbeddedFile。
处理这类样本的关键是别只看页面外观渲染,一定要过一遍对象结构分析。x86机器上跑pdfid和pdf-parser,是防止“看起来正常”的样本蒙混过关的有效手段。
6.3 别把在线预览当安全预览
有人觉得“我用网页版预览PDF就很安全”,这不一定成立。如果在线预览服务本身就是阅读器内核的云端版本,且没有沙箱隔离,恶意PDF的漏洞照样能在服务端触发。更隐蔽的是,攻击者可以构造这样一个PDF:在本地阅读器里显示正常的“发票”内容,在网页渲染器里却执行了恶意跳转。所以不要盲目相信某个“在线查看”就是绝对安全的,重要的文件尽量用可控的隔离环境查看。
6.4 遗忘的“哈希灭活”陷阱
分析完恶意PDF后,有个细节务必注意:归档样本前,一定要对文件做哈希锁定(记录MD5/SHA256),并且在共享给同事或上传沙箱平台时保留原始文件,不要用打开过、修改过的版本。PDF这种格式非常容易在编辑器“另存为”时被重新规范化,导致结构和哈希完全变化,后面做IOC匹配就对不上了。
我的习惯是:每个样本建立一个文件夹,文件名就是SHA256哈希,里面保存原始文件、分析日志、提取出的JS、网络IOC清单,这样就算隔了几个月再回头复盘,也能完整还原当时的判断过程。
6.5 遇到可疑PDF的标准处置流程速查
最后整理一份我在应急响应时常用的判断清单,可以直接抄作业:
| 场景 | 指标 | 处置动作 |
|---|---|---|
| 收到陌生PDF邮件,未打开 | 来源不明、附带诱导文案 | 不双击,上传沙箱/VirusTotal扫描 |
| PDF带/OpenAction+/JS | 自动触发脚本 | 判定高危,隔离样本,记录哈希 |
| JS里含编码混淆/堆喷 | 漏洞利用典型特征 | 转动态分析,确认是否已中招 |
| PDF释放exe/dll或者启动powershell | 恶意载荷落地 | 中断分析,恢复快照,全盘排查 |
| PDF发起外联通信 | C2连接 | 收集IOC,通知安全团队,阻断外联IP |
| 带AcroForm但JS简洁 | 可能是合法表单 | 结合来源判断,不轻易判黑 |
| 扫描件图片PDF但对象表异常 | 攻击者套壳 | 深挖对象引用,不只看视觉渲染 |
在安全这个行当干久了,你会发现自己越来越“不敢”随手打开附件。不是因为胆子小,而是见过太多攻击都是从一张PDF开始的。它用最无害的外表,承载着最危险的逻辑。搞清楚它的结构、手法和分析路径,不是为了制造恐慌,而是为了在恶意代码真正运行之前,我们就把它拦下来。
我在实际处理样本时的最大体会是:分析PDF恶意样本,七分靠细心三分靠工具。很多时候你不需要多高深的技术,只要你愿意在双击之前多看一眼对象结构、多跑一条pdfid命令,就已经挡住了绝大多数攻击。安全感的来源,从来不是杀毒软件有多强,而是你自己对风险有足够的认知。