之前我们学习了包括modbus、S7comm、DNP3等等工控领域的常用协议,从这篇开始,我们一步步开始,学习如何逆向真实的plc固件。

用到的固件为https://github.com/ameng929/NOE77101_Firmware

目前网上几篇对于该固件的分析都是以2018工控安全题目解题为主,并没有相应的知识和说明,这次我们不会做题目方面的说明,而是重点关注如何从零开始逆向固件。

本系列涉及到vxworks操作系统、PowerPC CPU架构的汇编语言、Ghrida的反编译问题,网上提供的资料较少,可能对于很多人来说是全新的知识。由于篇幅所限,也为了不让大家烦躁,我们暂且不对这两个新玩意儿进行长篇大论,我会将知识点穿插讲解。另外,建议大家使用windows系统,mac版的ida无法对PowerPC的固件进行函数识别,使用了好几个版本测试都存在该问题,原因暂时不明。

固件提取

这一步其实和路由器的固件逆向非常相似,厂商都会在官网提供固件的升级包,我们通过下载安装来更新设备的软件版本。不过路由器的版本更新相对于plc就频繁得多了,plc升级在大多数厂家的认识中是一件“麻烦”事(假如说一天一更新那工厂还干不干活了……),所以plc的固件是相对稳定的,我们选用的固件虽然看上去很“古老”,但如果考虑到我们现在不是进行漏洞挖掘,而是进行基础学习的话,就很合适了。

这里用到的工具就是大家熟知的binwalk,不过有所不同的是对于路由器来说,我们其实是“知根知底”的,因为架构和操作系统都还在我们的知识范围,而plc的操作系统和cpu就不那么友好了。

如图为binwalk操作某路由器固件,可以看到信息很多,包括文件系统等等一堆

如图为操作plc固件,基本可以说是啥信息都没有

直接binwalk -e NOE77101.bin提取即可,对分离出来的文件再一次binwalk就可以看到很多有用的信息了,包括固件采用的操作系统、内核版本、符号表等等信息

至此我们成功提取除了固件,要注意的是,这里操作系统是vxworks,cpu则是big endian的PowerPC,所以接下来分析工作并不是一帆风顺的,还需要经历诸多磨炼。

手动修复与自动修复

手动修复

我们将固件拖入ida准备开始分析

首先要修改处理器选项,因为ida并不能识别出来我们处理器的架构,我们需要将Processor type手动设置为PowerPC的big endian

接着ida还会让我们指定RAM和ROM的起始地址,在我们不知道的情况下,只能直接点击ok,剩下的选项都直接默认即可。其实这里已经有成熟的工具可以用来自动化识别了,但是考虑到我们是第一次实践,还是认认真真的尝试着走一遍。

进入后发现ida的函数少得可怜(mac版的就干脆一个也没有,笔者这里是在windows上保存了idb后在mac上操作的),而且一般的ida不支持对PowerPC的反编译,就很难受。当然,再难受也得硬着头皮看,先找到第一个函数。

看不懂?没事,我们一点点扣。首先这里大量使用到了r1处理器,在PPC(后文中PowerPC都会简称为PPC)中共有32个通用寄存器,虽然叫通用,但有一些寄存器和x86一样被“固化”了,比如r1就是栈寄存器,其余“固化”的还有:

  • r0,函数序言(function prologs)阶段使用,一般不需要我们关心
  • r2, toc指针,名字很高大上,其实就是系统调用时,标识系统调用号。
  • r3,返回值
  • r4-r10,参数,返回值较为特殊(比如乘法导致一个寄存器放不下的时候)时,r4也可以存放返回值
  • r11,用在指针的调用和当作一些语言的环境指针。
  • r12 ,用在异常处理和glink(动态连接器)代码。
  • r13 ,保留作为系统线程ID。
  • r14-r31,本地变量

还有好多好多寄存器,我们贯彻用到哪说哪的原则,这里就不再提了,需要我们再说。

lis r1,1
addi r1,r1,0

这两条指令可就有意思了,lis是加载立即数(load Immediate)的意思,它将16位的立即数传送到r1,然后再左移16位,在本条指令中就是把1放到了寄存器的第17位上,也就是0x10000了。

这里就冒出来问题了,我们很显然一般用的都是32位数(不管是编程还是寄存器长度,一般都是32位)啊,PPC的指令长度为4个字节,也就是32位,除去指令码和寄存器外,根本就放不下32位的数,难不成这里的1就是16位的1?

当然不是啦,这里就要看第二条指令了,addi的意思就是让将r1的低十六位+0的值给r1的低十六位,这样就是lis操作高十六位,addi再补上低十六位,成功实现了32位立即数的传送。

下面关于r3的操作同理,接着又是addi操作,将r1的值减了0x10,然后是b指令,b指令就是call了,调用一个函数的意思。还记得r1是干什么的吗?没错,r1是栈寄存器啊,这里开始将栈赋值,然后又对一个栈地址进行了减法操作意味着什么?

没错,一开始进行的实际上是栈的初始化,我们把它叫做initStack,接着的减法实际上就是在栈上开辟了空间(栈是高地址向低地址增长的),这里就应该是整个固件的初始化函数的一部分。

再查看官方手册,我们发现,初始化的栈的地址也就是System Image的地址,所以我们ida最开始选择的RAM、ROM的地址就应该是0x10000。我们重新用ida打开该程序,设置RAM、ROM,就可以发现识别的函数多了很多了。

这一步之后我们需要符号进行还原,毕竟谁也不愿意看着一堆sub_xxx来分析吧?这里我们就要用到binwalk告诉我们的符号表地址了(忘了吗?是0x301e74),我们用010editor打开固件,16进制形式分析,跳转到对应地址。

我们之前已经知道了这是vxworks5的固件,此类固件的符号表比较特殊,16字节为一组,分别表示的是符号字符串地址、符号所在地址、特殊标识(比如0x0500就是函数的意思)、0填充位,所以我们就可以按照这个规则来进行符号的修复。

脚本如下:(大家自行按照自己的固件地址对Start和end进行修改即可)

# coding:utf-8
from idaapi import *
import time eaStart = 0x31eec4
eaEnd = 0x348114
ea = eaStart
while ea < eaEnd:
offset = 0
MakeStr(Dword(ea - offset), BADADDR)
sName = GetString(Dword(ea - offset), -1, ASCSTR_C)
print sName
if sName:
eaFunc = Dword(ea - offset + 4)
MakeName(eaFunc,sName)
MakeCode(eaFunc)
MakeFunction(eaFunc,BADADDR)
ea = ea + 16
print"ok"

修复完后ida的function栏可以说是相当友好了。

自动修复

上面我们对固件进行了繁琐的分析最终才完成了对于函数表的修复,实际上,已经有大神开发了一款插件可以对Vxworks的固件进行自动的修复和分析,这就是平安的银河安全实验室开发的vxhunter,大家可以去github自行下载(感谢大佬们的奉献)。

https://github.com/dark-lbp/vxhunter

之前我们手动分析用的是ida,自动部分我们就用Ghidra来进行(还有一个最大的好处是Ghidra支持对PowerPC的反汇编,所以之后的分析我们都采用Ghidra来进行)。

按照readme安装vxhunter,然后打开windows选项卡,选择scriptManager,运行脚本即可

耐心等待一段时间,即可完成

是不是非常简单?下一步就可以开始进行分析了。

小试牛刀

首先我们跳转到之前简单分析的initStack(要注意之前是0x4c,但是我们把基址设置为0x10000后地址应该是0x1004c)

可以看到在initStack操作后,进行了跳转,目标是usrInit,还记得参数的传递规则吗?没错,这里的r3就是为了传递参数的,可以看到,和我们之前说到的一样,还是使用lis和addi的搭配进行32位整数的传递

当然其实再往上看我们还可以发现r4寄存器的身影,因为PowerPC不像x86的stdcall,能通过push来判断哪些是函数的参数,所以我们只能是把r3~r10的身影都锁定,宁可错杀,不可放过。(我们也可以通过查看后续函数的调用来猜测,不过也是猜测,还是多注意为好)

这里可能有些人一看就慌神了,“我靠,这都不认识,怎么看啊?”,不慌不慌,其实这里的调用十分有规律,我们拆开看看

00010018 3c 80 04 00     lis        r4,0x400
0001001c 38 84 00 00 addi r4,r4,0x0
00010020 7c 90 8b a6 mtspr IC_CSR,r4
00010024 7c 98 8b a6 mtspr DC_CST,r4

可以看到,实际上是进行了三组同样的操作,只不过是r4的值不一样罢了,我们选择一组来进行分析。

首先打头的还是lis和addi的组合,将r4的值设置为0x4000000,然后是mtspr指令,指令格式如下:

mtspr      spc_reg,reg

spc_reg是特殊寄存器的意思,指令将reg寄存器的内容传个一个特殊寄存器,所以这条指令的意思就是将0x4000000赋给IC_CSR寄存器。可以看到,这个r4就是个中间商,那么和之后的参数传递应该是没有关系的。

看到这里有些同学可能就会说了:“你不是说Ghidra有反汇编功能吗?那你还费劲让我们看这个?”,哎,别急别急,我们就来看看Ghidra给我们的反汇编是什么样的

它就分析出了三条,instructionSynchronize()实际上对应的指令是isync,也就是指令同步的意思,并不关键,TLBInvalidateAll()实际上对应的指令是tlbia,也就是快表的相关操作(快表不知道的去翻大二专业课《操作系统》吧,文章内实在没地方讲了),而最后就是函数调用了。

有没有发现?这反编译显然是把我们的mtspr给落下了,直接将r4当成了参数,导致usrInit有了俩参数!其实我们在刚才的探索中很明显看到r4在这绝对没有起到参数传递的功能。这就说明了对于PowerPC的反编译,ida干不了,但Ghrida干得也不是很好,所以在之后的分析中我们要时刻留意,绝对不能只看反编译结果。

接着我们调到usrInit函数来看看,还是先看汇编部分

这部分是大家熟悉的函数序言,我们简单分析一下。

stwu r1,local_18(r1)

意思就是将r1的内容送到r1+local_18的地址中,r1我们说过是栈顶指针。

mfspr r0,LR

这句话在ida上会被翻译为mflr r0,就是讲LR的值给了r0,LR寄存器是记录函数返回地址的寄存器

stw r31,local_4(r1)

和第一句格式相同,是将r31的内容送到r1+local4的地址中

stw r0,local_res4(r1)

这里local_res4是个正数,也就是说将r0送到了一个栈基址往上的地方,在想到r0内容是函数返回地址,是不是就清晰了?实际上就是相当于x86中call指令将函数返回地址保存在栈上的操作。

or r31,r1,r1

or指令的操作是将第二个操作数与第三个操作数or后保存进第一个数中。那就有人要问了,r1和r1进行了or之后不还是r1吗?好问题,实际上这种格式就是PPC的mov指令,写成x86就是mov r31,r1,为什么这样大费周折呢?这里我猜测是为了提高效率。

说到这是不是大家已经脑补出了函数序言的基本行为?实际上和x86并没有什么本质上的区别。

继续来看该函数

又是一堆函数,那我们就先看看第一个吧,它以param1作为参数,而param1就是r3,前面分析了是0

首先是bzero操作,参数是开始地址和结束地址,将中间部分都置为0,之后又这段地址将用来保存数据。

sysStartType是系统的启动类型,包括有bootram启动和rom启动,压缩式和非压缩式等等,这里受篇幅所限,先不细讲了。

intVecBaseSet是非常非常非常重要的一个函数,不知道大家看到intVec有没有想起来在《Windows调试艺术》中我们说过有个东西叫中断向量表(Interrupt Vector Table),其实就是那玩意,这个函数就是用来设置起始地址的,这里的起始地址固定为0。

返回到usrInit我们又看见下面还一个名字中带Vec的啊,那正好再来看看这个伙计

是不是又觉得有点难了?别慌,慢慢来,首先是var2变成了地址,而后又大量使用var2的数组形式,那就让我们瞅瞅地址指向了啥

哦,var2[0]是个数,var2[1]是个地址,指向的是个函数,var2[2]是个地址,指向的是个结构体。

往后走首先是if检验excExhandle(也就是var2[2])是不是为NULL,不是继续,显然这里不是,那就继续。

接着是个非常怪异的循环,涉及到指针、地址、结构体、结构体指针的变换,可能会很难,我尽量说的简单一些。

首先找迭代变量,这里很显然是*var1,而var1是啥?var1是var2+5,var2加5(这里的加5是地址加5,不是真的+5),看看图中地址就知道了,它跑到了handle,也就是说var1在验证handle是否为空。

var2同样在迭代,不过它是每次+3,在看图,也就是到了0x200那个地方,下面又是一个相同的结构。

最终是以*var2、var2[2]为参数调用var[1],也就是excExcHandle(data,excExHandle),在迭代不断地进行该函数,完成Interrupt Vector Table的初始化工作。

总结

可以看到,plc固件的逆向涉及到了非常非常多的新知识,由于篇幅所限,这次我们仅仅是完成了最基本的工作,接下来我们会一点点深入,直到彻底吃透该固件。

工控安全入门(五)—— plc逆向初探的更多相关文章

  1. 工控安全入门(六)——逆向角度看Vxworks

    上一篇文章中我们对于固件进行了简单的分析,这一篇我们将会补充一些Vxworks的知识,同时继续升入研究固件内容. 由于涉及到操作系统的内容,建议大家在阅读本篇前有一定操作系统知识的基础,或者是阅读我的 ...

  2. 工控安全入门(二)—— S7comm协议

    在上一次的文章中我们介绍了施耐德公司的协议modbus,这次我们把目标转向私有协议,来看看另一家巨头西门子的S7comm.首先要说明,这篇文章中的内容有笔者自己的探索,有大佬们的成果,但由于S7com ...

  3. 工控安全入门之 Ethernet/IP

    工控安全入门之 Ethernet/IP Ethernet/IP 与 Modbus 相比,EtherNet/IP 是一个更现代化的标准协议.由工作组 ControlNet International 与 ...

  4. 工控安全入门(三)—— 再解S7comm

    之前的文章我们都是在ctf的基础上学习工控协议知识的,显然这样对于S7comm的认识还不够深刻,这次就做一个实战补全,看看S7comm还有哪些值得我们深挖的地方. 本篇是对S7comm的补全和实战,阅 ...

  5. 工控安全入门(七)—— plc的网络

    上一篇我们详细分析了bootram和Vxworks的基本启动流程,这篇文章中我们把视线转到plc的网络部分,同时来复现我们第一个.第二个工控安全漏洞. VxWorks的网络设备驱动 一般我们说有三种设 ...

  6. 工控安全入门之Ethernet/IP

    这一篇依然是协议层面的,协议层面会翻译三篇,下一篇是电力系统中用的比较多的DNP3.这一篇中大部分引用的资料都可以访问到,只有一篇reversemode.com上的writeup(http://rev ...

  7. 工控安全入门之Modbus(转载)

    工控安全这个领域比较封闭,公开的资料很少.我在读<Hacking Exposed Industrial Control Systems>,一本16年的书,选了的部分章节进行翻译,以其抛砖引 ...

  8. 工控安全入门(一)—— Modbus协议

    modbus基础知识 modbus协议最初是由Modicon公司在1971年推出的全球第一款真正意义上用于工业现场的总线协议,最初是为了实现串行通信,运用在串口(如RS232.RS485等)传输上的, ...

  9. [工控安全]西门子S7-400 PLC固件逆向分析(一)

    不算前言的前言:拖了这么久,才发现这个专题没有想象中的简单,学习的路径大致是Step7->S7comm->MC7 code->firmware,我会用尽量简短的语言把前两部分讲清楚, ...

随机推荐

  1. git 使用案例(本地仓库无缝迁移远程仓库)

    之前都是直接从gitlab上clone代码,然后把本地代码copy过去,然后push.有点麻烦,查询了一下如何无缝从本地仓库迁移到远程仓库.记录一波... 下面的例子采用github来做例子. 1. ...

  2. 8种形式的Android Dialog使用举例

    在Android开发中,我们经常会需要在Android界面上弹出一些对话框,比如询问用户或者让用户选择.这些功能我们叫它Android Dialog对话框,在我们使用Android的过程中,我归纳了一 ...

  3. day 56 Django基础五之django模型层(二)多表操作

    Django基础五之django模型层(二)多表操作   本节目录 一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询.分组查询.F查询和Q查询 六 ORM ...

  4. python字符串的索引切片和常用操作方法,for循环

    ---恢复内容开始--- 一.字符串的索引与切片 1.索引 s = 'ASDFGHJKL' 有序序列,索引--index:从0开始 s1 = s[0],取出单个元素:A: s1是个全新的字符串和原字符 ...

  5. <Django> MVT三大块之Models(模型)

    1.ORM(对象-关系-映射)---面向对象,不需要面向SQL语句 根据对象的类型生成表结构 将对象.列表的操作,转化成SQL语句 将SQL语句查询的结果转化成对象.列表 目的:实现数据模型与数据库的 ...

  6. jQuery链式编程时修复断开的链

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. bigcolorpicker 颜色拾取器插件——例

    参考:http://bigui4.sinaapp.com/picker/colorpicker.html 效果: html: <!DOCTYPE html> <html> &l ...

  8. hadoop 环境下不知道yarn端口可以通过此命令查找

    yarn jar hadoop-examples-2.6.0-mr1-cdh5.10.0.jar pi 1 30 hadoop-examples-2.6.0-mr1-cdh5.10.0.jar 此JA ...

  9. ASCII专用测试字符串

    这段是废话: 在很多时候不同语言所写的不同终端中 经常会有字符串转码的问题 常用一下字符串测试不同终端的输出可以快速匹配和修改默认转码 正文: !""""#$% ...

  10. centos 7 开机启动服务项优化

    1. 使用 systemctl list-unit-files 可以查看启动项 systemctl list-unit-files | grep enable 过滤查看启动项如下 abrt-ccpp. ...