[工控安全][原创]SIEMENS SIMATIC STEP7软件中关键DLL文件分析(二)

mailto:wangkai0351@gmail.com
【未经赞成禁止转载】git

由于软件著做权和软件用户协议的限制,咱们没有办法对STEP7软件的关键DLL文件进行逆向工程,或者动态调试,这可真是一个头疼的问题。github

咱们能够考虑另外一条更为狭窄的逆向之路了——借助震网病毒的研究成果。编程

赛门铁克的震网STUXNET病毒分析报告中声称,函数

震网病毒是替换掉Step7软件中S7OTBXDX.DLL动态连接库文件,劫持掉该DLL文件包含了一些对PLC编程和调试有关键功能的函数,完整列出ui

  • s7db_open
  • s7db_close
  • s7blk_read
  • s7blk_write
  • s7ag_link_in
  • s7blk_findnext
  • s7blk_findfirst
  • s7blk_delete
  • s7ag_read_szl
  • s7_event
  • s7ag_test
  • s7ag_stop
  • s7ag_bub_cycl_read_create
  • s7ag_bub_read_var
  • s7ag_bub_write_var
  • s7ag_bub_read_var_seg
  • s7ag_bub_write_var_seg

github上有项目公开了一部分震网病毒STUXNET的反编译结果,震网病毒和咱们关心的s7otbxdx.dll关系很大,因此咱们尝试从震网病毒样本中了解s7otbxdx.dll的内部构造。this

https://github.com/Laurelai/decompile-dump调试

咱们观察到项目中789F6F8DE3F140CF5D73BEF0B8ABAF78.c文件中,确实列出了一些咱们上面罗列的s7簇函数。code

毫无疑问,使用STEP7软件中的某些功能,就会触发这些函数,上位机经过s7comm协议和PLC交换状态和数据。orm

我抽一个s7ag_read_szl函数/功能来细致研究一下。对象

赛门铁克的病毒分析报告《W32.Stuxnet Dossier》中声称

Used to query PLC information, through a combination of an ID and an index (it can be used for instance to get the PLC type.) The export modifies the API’s return information if it’s called with specific ID=27, index=0.

能够看出来s7ag_read_szl函数的功能是读取PLC的系统状态列表(read system status list,status在德文中是以'z'开头的,因此szl就沿用到了如今)。

可是赛门铁克报告中说震网用了SZL-id=27/index=0,这个我是很难理解的。

按照《SIMATIC 用于S7-300/400系统和标准函数的系统软件 卷1/2》和wireshark s7comm dissector的表述,我更倾向于震网用了SZL-id=0x1c/index=0用于识别PLC的系列是否是6es7 315-2。

下面是发送s7ag_read_szl(SZL-id=0x1c/index=0)后,PLC响应报文。

0000   ff 09 00 d6 00 1c 00 00 00 22 00 0a 00 01 53 37   ÿ..Ö....."....S7
0010   33 30 30 2f 45 54 32 30 30 4d 20 73 74 61 74 69   300/ET200M stati
0020   6f 6e 5f 31 00 00 00 00 00 00 00 00 00 00 00 02   on_1............
0030   50 4c 43 5f 31 00 00 00 00 00 00 00 00 00 00 00   PLC_1...........
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0050   00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0060   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0070   00 00 00 04 4f 72 69 67 69 6e 61 6c 20 53 69 65   ....Original Sie
0080   6d 65 6e 73 20 45 71 75 69 70 6d 65 6e 74 00 00   mens Equipment..
0090   00 00 00 00 00 05 53 20 43 2d 42 31 55 33 39 33   ......S C-B1U393
00a0   31 34 32 30 31 31 00 00 00 00 00 00 00 00 00 00   142011..........
00b0   00 00 00 00 00 00 00 07 43 50 55 20 33 31 35 2d   ........CPU 315-
00c0   32 20 50 4e 2f 44 50 00 00 00 00 00 00 00 00 00   2 PN/DP.........
00d0   00 00 00 00 00 00 00 00 00 08                     ..........

三重证据证实了,赛门铁克报告中的描述可能有错,除非SZL-id=27/index=0是一个隐藏的功能(我未了解)。

下面,咱们尝试剖开s7ag_read_szl函数内部看看(相比s7ag*函数簇中的其余函数,这个函数是最简单的)。

int __cdecl s7ag_read_szl()
{
  int v1; // eax@1

  v1 = sub_1000BE40((int)sub_100026EA);
  return ((int (*)(void))v1)();
}

sub_1000BE40函数的功能是,把这个函数劫持到sub_100026EA函数处。

void __stdcall sub_100026EA(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
  int v8; // eax@1
  char v9; // [sp+0h] [bp-28h]@1
  int v10; // [sp+10h] [bp-18h]@1
  char v11; // [sp+14h] [bp-14h]@1
  char *v12; // [sp+18h] [bp-10h]@1
  int v13; // [sp+24h] [bp-4h]@1

  v12 = &v9;
  sub_1000F9F2();
  v13 = 0;
  v8 = sub_1000D2B6(a1, a2, a3, a4, a5, a6, a7, a8);
  unknown_libname_13(v8);
  LOBYTE(v13) = 1;
  v10 = sub_100034DB(&v11);
  LOBYTE(v13) = 0;
  sub_100034F7((int *)&v11);
  JUMPOUT(*(unsigned int *)loc_10002758);//函数执行最后,调回loc_10002758,猜想这是s70tbxdx.dll中s7ag_read_szl函数的真实内存地址
}

sub_100026EA函数传入了a1~a8一共八个参数,这就有点意思了,由于调用它的s7ag_read_szl一个参数都没有。咱们有理由猜想:这八个参数中,必定有表示SZL-id/index的两个参数。到底是那两个呢,咱们近距离观察sub_1000D2B6函数,由于八个参数又所有传到了这个函数里。

int __cdecl sub_1000D2B6(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
  int result; // eax@1
  int v9; // ecx@1

  v9 = (int)operator new(0x40u);
  result = 0;
  if ( v9 )
    result = sub_1000906E(v9, a1, a2, a3, a4, a5, a6, a7, a8);
  return result;
}

这个函数先new分配了一块内存,int v9[64],把首地址v9连带八个参数又传入了sub_1000906E函数。

int __thiscall sub_1000906E(int this, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)
{
  int v10; // esi@1

  v10 = this;
  *(_DWORD *)this = &off_1001C428;//还出如今s7blk_read函数中 对象的函数成员 构造函数?
  *(_DWORD *)(this + 40) = a5;
  *(_DWORD *)(this + 44) = a6;
  *(_DWORD *)(this + 48) = a7;
  *(_DWORD *)(this + 52) = a8;
  *(_DWORD *)(this + 32) = a3;
  *(_DWORD *)this = &off_100211D8;//对象的函数成员 析构函数?
  *(_DWORD *)(this + 28) = a2;
  *(_DWORD *)(this + 36) = a4;
  *(_DWORD *)(this + 56) = a9;
  sub_10003B6C(this + 4, a2, a3, a4);
  return v10;
}

真的不容易,终于看到s7ag_read_szl这个函数是怎么样构造报文了,可是函数最后又调用了sub_10003B6C。

int __thiscall sub_10003B6C(int this, int a2, int a3, int a4)
{
  int result; // eax@1

  *(_DWORD *)this = a2;
  *(_DWORD *)(this + 4) = a3;
  result = a4;
  *(_DWORD *)(this + 8) = a4;
  return result;
}

咱们把sub_1000906E函数和sub_10003B6C函数,这一连串的结构体/对象构造过程看一下,很明显sub_10003B6C说明this+4这里又有一个小结构体/对象,属于has a关系,有多是继承?

其实,我犯了一个错误,我忘记了如今只是在分析震网病毒,这里只是构造了一个结构体/对象,并非最终报文的形式。这个结构体是s7otbxdx.dll与调用dll的进程交换数据的一种数据格式,咱们简单把它叫作S7otbxdx_exchangeBlock。只能大胆猜想,S7otbxdx_exchangeBlock和报文格式有很大关联度,可是不能说二者就是一一对应的关系。

下面,咱们尝试逆向分析这个S7otbxdx_exchangeBlock的结构体/对象。

首先,S7otbxdx_exchangeBlock的空间是由new创造的,那么先由new的大小,统计一下,有几种S7otbxdx_exchangeBlock。由于震网病毒只劫持了十几个函数,因此咱们只能从这些函数中一窥究竟。

sizeof(S7otbxdx_exchangeBlock) 相关函数 基类
0x40u s7ag_read_szl sub_10003B6C(this + 4, a2, a3, a4)
s7ag_bub_read_var sub_10003B83(this + 4, a2, a3)
s7ag_bub_write_var sub_10003B83(this + 4, a2, a3)
0x48u s7blk_read sub_10003B6C(this + 4, a2, a3, a4)
s7blk_findfirst sub_10003B6C(this + 4, a2, a3, a4)
s7blk_findnext sub_10003B6C(this + 4, a2, a3, a4)
s7_event sub_10003B6C(this + 4, a2, a3, a4)
0x4cu s7blk_delete sub_10003B6C(this + 4, a2, a3, a4)
0x38u s7blk_write sub_10003B6C(v10 + 4, *(_DWORD )(v10 + 28), (_DWORD )(v10 + 32), (_DWORD *)(v10 + 36));
0x30u s7ag_link_in sub_10003B6C(this + 4, a2, a3, a4)
0x44u s7ag_bub_read_var_seg sub_10003B83(this + 4, a2, a3)
s7ag_bub_write_var_seg sub_10003B83(this + 4, a2, a3)
0x50u S7ag_bub_cycl_read_create sub_10003B83(this + 4, a2, a3)
0x70u S7ag_test sub_10003B83(this + 4, a2, a3)

其实我猜想,这些对象包含的基类颇有多是它们对应报文的参数段(parameter)的一些字段。

为何这么说呢?之后咱们从报文的特征来对应一下。

这样,咱们简单认识了震网病毒样本中的s7ag_read_szl函数,固然咱们假设STEP7 s7otbxdx.dll中的s7ag_read_szl函数也长这个样子。

至于事实上究竟如何?不让反编译和动态调试s7otbxdx.dll,也没办法了解。

相关文章
相关标签/搜索