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)
",hmod);
SCP_open=GetProcAddress(hmod,"SCP_open");
if (daveDebug & daveDebugOpen)
LOG2("GetProcAddress returned %p)
",SCP_open);
SCP_close=GetProcAddress(hmod,"SCP_close");
if (daveDebug & daveDebugOpen)
LOG2("GetProcAddress returned %p)
",SCP_close);
SCP_get_errno=GetProcAddress(hmod,"SCP_get_errno");
if (daveDebug & daveDebugOpen)
LOG2("GetProcAddress returned %p)
",SCP_get_errno);
SCP_send=GetProcAddress(hmod,"SCP_send");
if (daveDebug & daveDebugOpen)
LOG2("GetProcAddress returned %p)
",SCP_send);
SCP_receive=GetProcAddress(hmod,"SCP_receive");
if (daveDebug & daveDebugOpen)
LOG2("GetProcAddress returned %p)
",SCP_receive);
SetSinecHWnd=GetProcAddress(hmod,"SetSinecHWnd");
if (daveDebug & daveDebugOpen)
LOG2("GetProcAddress returned %p)
",SetSinecHWnd);
en=SCP_get_errno();
h=SCP_open(accessPoint);
en=SCP_get_errno();
LOG3("handle: %d error:%d
", 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_send
和SCP_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中的两个函数,以近乎黑盒的形式研究这些函数。