1. 如何选择需要感染的PLC。
Stuxnet会根据目标系统的特点,使用不同的代码来感染PLC。 一个感染的序列包括了许多PLC 模块(代码模块和数据模块),用以注入PLC来改变目标PLC 的行为。这个威胁包括了三个感染序列。其中两个非常相似,功能也相同,我们将其命名为序列A和B。第三个序列我们命名为序列C。Stuxnet通过验证“指纹”来判断系统是否为计划攻击的目标。它会检查: PLC种类/家族:只有CPU 6ES7-417 和6ES7-315-2 会被感染。系统数据模块:SDB 会被解析;根据他们包含的数据,感染进程会选择A,B或其它感染方式开始行动。当解析SDB 时,代码会搜索这两个值是否存在-- 7050h and 9500h;然后根据这两个数值的出现次数,选择序列A 或B 中的一种来感染PLC。 代码还会在SDB 模块的50h 子集中搜索字节序2C CB 00 01, 这个字节序反映了通信处理器CP 342-5 (用作Profibus-DP) 是否存在。 而选择序列C进行感染的条件则由其他因素构成。
2. 感染方法
Stuxnet使用“代码插入”的感染方式。当Stuxnet 感染OB1时,它会执行以下行为: 增加原始模块的大小; 在模块开头写入恶意代码; 在恶意代码后插入原始的OB1 代码。Stuxnet也会用类似于感染OB1的方式感染OB35。它会用自身来取代标准的协同处理器DP_RECV 代码块,然后在Profibus (一个标准的用作分布式I/O的工业网络总线) 中挂钩网络通信。 利用A/B方法的感染步骤如下: 检查PLC 类型; 该类型必须为S7/315-2; 检查SDB 模块,判断应该写入序列A 或B 中的哪一个; 找到DP_RECV,将其复制到FC1869,并用Stuxnet嵌入的一个恶意拷贝将其取代; 在序列中写入恶意模块(总共20个),由Stuxnet 嵌入; 感染OB1,令恶意代码可以在新的周期开始时执行; 感染OB35, 它将扮演“看门狗”的角色。
3. 感染代码
被注入OB1 功能的代码是用来感染序列A 和B的。这些序列包含了以下模块: 代码块:FC1865 至FC1874, FC1876 至FC1880 (注意:FC1869并非Stuxnet的一部分,而是PLC的DP_RECV模块的一个拷贝); 数据模块:DB888 至DB891。 序列A 和B 用DP_RECV 挂钩模块来拦截Profibus 中的数据包,并根据在这些模块中找到的数值,来构造其他的数据包并发送出去。这由一个复杂的状态机控制(状态机被建立在上面提到的FC 模块中)。这个状态机可部分受控于数据块DB890 中的DLL。 在某些条件下,序列C会被写入一个PLC。这个序列比A和B包含更多的模块: FC6055 至FC6084;DB8062, DB8063;DB8061, DB8064 至DB8070 (在运行中产生)。 序列C主要为了将I/O信息读写入PLC的内存文件映射的I/O 区域,以及外围设备的I/O。 程序A/B 的控制流如下图所示,在之前的Step7 编辑器的截图中也有部分显示(数据模块FC1873)
4. Rootkit Stuxnet PLC rootkit代码全部藏身于假冒的s7otbxdx.dll中
为了不被PLC所检测到,它至少需要应付以下情况: 对自己的恶意数据模块的读请求;对受感染模块(OB1 , OB35, DP_RECV) 的读请求;可能覆盖Stuxnet自身代码的写请求。 Stuxnet包含了监测和拦截这些请求的代码,它会修改这些请求以保证Stuxnet 的PLC 代码不会被发现或被破坏。下面列出了几个Stuxnet用被挂钩的导出命令来应付这些情况的例子: s7blk_read: 监测读请求,而后Stuxnet 会返回:真实请求的DP_RECV (保存为FV1869);错误信息,如果读请求会涉及到它的恶意模块;OB1或OB35的干净版本的拷贝s7blk_write: 监测关于OB1/OB35的写请求,以保证他们的新版本也会被感染。s7blk_findfirst / s7blk_findnext: 这些例程被用于枚举PLC中的模块。恶意模块会被自动跳过。s7blk_delete: 监测对模块的“删除”操作。 如上文所述,Stuxnet 是一个非常复杂的威胁,而其中的PLC 感染代码令问题更加难以解决。仅仅关于注入的MC7代码(我们于几个月前通过逆向工程获得)就可以讨论很久。若想了解更多关于PLC 感染例程和Stuxnet的总体情况,请务必关注我们即将于Virus Bulletin会议中发布的白皮书。