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

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

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

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

  • s7db_open and s7db_close
  • s7ag_read_szl
  • s7_event
  • s7ag_test
  • s7ag_link_in
  • s7ag_bub_cycl_read_create
  • s7ag_bub_read_var
  • s7ag_bub_write_var
  • s7ag_bub_read_var_seg
  • s7ag_bub_write_var_seg

programm files/system路径下找到了这个文件,可是Step7软件的用户协议中禁止了对其进行逆向分析数组

被许可方无权对软件进行修改、反编译或逆向工程。并且也不能提取任何单独的部分,除非强制的版权法容许。网络

这样,咱们没有权利反编译它了。可是有权调试它?函数

**(咨询西门子中国工业客户服务中心,得知包括使用ollydbg和windbg在内的工具对工程软件后台运行的探测行为,属于*《用户协议》中"逆向工程”范畴)**工具

我仍是从网络上找到了一些蛛丝马迹,好比著名的逆向分析S7comm协议的成果——LIBNODAVE开源软件(github地址)中调用了Step7的另外一个DLLS7ONLINX.DLL指针

LIBNODAVE调用了S7ONLINX.DLL其中的几个函数(或者称它们为SCP_函数簇,经过串口走S7comm on MPI协议)调试

SCP_opencode

SCP_close

SCP_get_errno

SCP_send

SCP_receive

SetSinecHWnd

具体的调用方式的代码以下

EXPORTSPEC HANDLE DECL2 openS7online(const char * accessPoint, HWND handle) {
    HMODULE hmod;
    int h,en;
    _setHWnd SetSinecHWnd; 

    hmod=LoadLibrary("S7onlinx.dll");
    if (daveDebug & daveDebugOpen)
    LOG2("LoadLibrary(S7onlinx.dll) returned %p)\n",hmod);

    SCP_open=GetProcAddress(hmod,"SCP_open");
    if (daveDebug & daveDebugOpen)
        LOG2("GetProcAddress returned %p)\n",SCP_open);

    SCP_close=GetProcAddress(hmod,"SCP_close");
    if (daveDebug & daveDebugOpen)
    LOG2("GetProcAddress returned %p)\n",SCP_close);

    SCP_get_errno=GetProcAddress(hmod,"SCP_get_errno");
    if (daveDebug & daveDebugOpen)
    LOG2("GetProcAddress returned %p)\n",SCP_get_errno);

    SCP_send=GetProcAddress(hmod,"SCP_send");
    if (daveDebug & daveDebugOpen)
    LOG2("GetProcAddress returned %p)\n",SCP_send);

    SCP_receive=GetProcAddress(hmod,"SCP_receive");
    if (daveDebug & daveDebugOpen)
    LOG2("GetProcAddress returned %p)\n",SCP_receive);
    
    SetSinecHWnd=GetProcAddress(hmod,"SetSinecHWnd");
    if (daveDebug & daveDebugOpen)
    LOG2("GetProcAddress returned %p)\n",SetSinecHWnd);

    en=SCP_get_errno();
    h=SCP_open(accessPoint);
    en=SCP_get_errno();
    LOG3("handle: %d  error:%d\n", h, en);
    SetSinecHWnd(h, handle);
    return h;
};

EXPORTSPEC HANDLE DECL2 closeS7online(int h) {
    SCP_close(h);
}

调用上述代码中定义的openS7online函数和closeS7online函数的方式可参考testS7online.c文件

fds.rfd=openS7online(argv[adrPos], GetConsoleHwnd());//第二个参数是绑定到一个窗口上
closeS7online(fds.rfd);

能够看出,调用openS7online函数只是把SCP_函数簇加载到或者说注入到进程的内存空间中,像SCP_open就至关于这个函数在当前进程中的函数入口地址了,因此在main函数开头解析完调输入参数后就要调用,其第一个参数argv[adrPos]是一个字符串指针,表明access point,也就是上位机串口的接入点;第二个参数,是绑定到执行main函数当前的窗口。

当前,已经把SCP_函数簇加载到内存空间中了,下面咱们接着看testS7online.c文件是如何调用SCP_函数簇经过串口发送报文数据的。咱们的目的是,了解SCP_函数簇的输入和输出参数是什么?

nodave.c文件中,经过分别包装SCP_sendSCP_receive写了两个函数。

先看SCP_send函数

//nodave.c
int DECL2 _daveSCP_send(int fd, uc * reqBlock) {
    S7OexchangeBlock* fdr;
    fdr=(S7OexchangeBlock*)reqBlock;
    fdr->headerlength=80;
    fdr->allways2 = 2;
    fdr->payloadStart= 80;
    return SCP_send(fd, fdr->payloadLength+80, reqBlock);
}

能够看到给SCP_send函数传递了三个参数,分别是

类型 说明(本身胡猜)
int 发送报文使用串口的地址
uc/unsigned char 发送报文长度,具体报文的结构定义还不肯定
uc/unsigned char 发送报文数组的其实地址

函数返回类型是int,本身胡猜,多是函数执行成功否?

再看SCP_receive函数

//nodave.c
int daveSCP_receive(int h, uc * buffer) {
    int res, datalen;
    S7OexchangeBlock * fdr;
    fdr=(S7OexchangeBlock*) buffer;
    res=SCP_receive(h, 0xFFFF, &datalen, sizeof(S7OexchangeBlock), buffer);
    if (daveDebug & daveDebugByte) {
    _daveDump("header:",buffer, 80);
    _daveDump("data:",buffer+80, fdr->payloadLength);
    _daveDump("data:",buffer+80, fdr->payloadLength);
    }   
    return res; 
}

能够看到给SCP_receive函数传递了五个参数,分别是

类型 说明(本身胡猜)
int 接收报文使用串口的地址(一般状况下和发送报文用一个串口?)
int 0xFFFF定值?
*int 接收报文的长度
int S7OexchangeBlock是s7onlinx.dl和其调用者之间交换数据块的结构体
uc/unsigned char 接收报文数据的起始地址

返回参数是int,本身胡猜,多是函数执行成功否?

至今,咱们以LIBNODAVE开源软件了解了STEP7的一个DLL文件中两个关键函数的接口,以及研究者本身如何调用该DLL中的两个函数,以近乎黑盒的形式研究这些函数。