Sec

网络安全研究员,专注于工业互联网安全领域。求职中。

导航

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

mailto:wangkai0351@gmail.com
【未经同意禁止转载】

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

震网病毒是替换掉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_open

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中的两个函数,以近乎黑盒的形式研究这些函数。

posted on 2019-06-21 15:44  大单GreatDane  阅读(727)  评论(0编辑  收藏  举报