initial implementation of mitmfapi, thanks @xtr4nge :D

This commit is contained in:
byt3bl33d3r 2015-06-19 17:39:49 +02:00
commit c5ad00822b
13 changed files with 267 additions and 337 deletions

View file

@ -14,6 +14,11 @@ class ConfigWatcher(FileSystemEventHandler):
_instance = None _instance = None
config = ConfigObj("./config/mitmf.conf") config = ConfigObj("./config/mitmf.conf")
def __init__(self):
observer = Observer()
observer.schedule(self, path='./config', recursive=False)
observer.start()
@staticmethod @staticmethod
def getInstance(): def getInstance():
if ConfigWatcher._instance is None: if ConfigWatcher._instance is None:
@ -21,11 +26,6 @@ class ConfigWatcher(FileSystemEventHandler):
return ConfigWatcher._instance return ConfigWatcher._instance
def startConfigWatch(self):
observer = Observer()
observer.schedule(self, path='./config', recursive=False)
observer.start()
def getConfig(self): def getConfig(self):
return self.config return self.config

99
core/mitmfapi.py Normal file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env python2.7
# Copyright (c) 2014-2016 Moxie Marlinspike, Marcello Salvati
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
#
"""
Coded by @xtr4nge
"""
#import multiprocessing
import threading
import logging
import json
import sys
from flask import Flask
from core.sergioproxy.ProxyPlugins import ProxyPlugins
app = Flask(__name__)
log = logging.getLogger('werkzeug')
log.setLevel(logging.DEBUG)
class mitmfapi:
@app.route("/")
def getPlugins():
# example: http://127.0.0.1:9090/getPlugins
pdict = {}
#print ProxyPlugins.getInstance().plist
for activated_plugin in ProxyPlugins.getInstance().plist:
pdict[activated_plugin.name] = True
#print ProxyPlugins.getInstance().plist_all
for plugin in ProxyPlugins.getInstance().plist_all:
if plugin.name not in pdict:
pdict[plugin.name] = False
#print ProxyPlugins.getInstance().pmthds
return json.dumps(pdict)
@app.route("/<plugin>")
def getPluginStatus(plugin):
# example: http://127.0.0.1:9090/getPluginStatus/cachekill
for p in ProxyPlugins.getInstance().plist:
if plugin == p.name:
return json.dumps("1")
return json.dumps("0")
@app.route("/<plugin>/<status>")
def setPluginStatus(plugin, status):
# example: http://127.0.0.1:9090/setPluginStatus/cachekill/1 # enabled
# example: http://127.0.0.1:9090/setPluginStatus/cachekill/0 # disabled
if status == "1":
for p in ProxyPlugins.getInstance().plist_all:
if (p.name == plugin) and (p not in ProxyPlugins.getInstance().plist):
ProxyPlugins.getInstance().addPlugin(p)
return json.dumps({"plugin": plugin, "response": "success"})
elif status == "0":
for p in ProxyPlugins.getInstance().plist:
if p.name == plugin:
ProxyPlugins.getInstance().removePlugin(p)
return json.dumps({"plugin": plugin, "response": "success"})
return json.dumps({"plugin": plugin, "response": "failed"})
def startFlask(self, host='127.0.0.1', port=9090):
app.run(host=host, port=port)
#def start(self):
# api_thread = multiprocessing.Process(name="mitmfapi", target=self.startFlask)
# api_thread.daemon = True
# api_thread.start()
def start(self):
api_thread = threading.Thread(name='mitmfapi', target=self.startFlask)
api_thread.setDaemon(True)
api_thread.start()

View file

@ -42,32 +42,25 @@ class ProxyPlugins:
in handleResponse, but is still annoying. in handleResponse, but is still annoying.
''' '''
_instance = None _instance = None
plist = []
mthdDict = {"connectionMade": "clientRequest", mthdDict = {"connectionMade": "clientRequest",
"handleStatus": "serverResponseStatus", "handleStatus": "serverResponseStatus",
"handleResponse": "serverResponse", "handleResponse": "serverResponse",
"handleHeader": "serverHeaders", "handleHeader": "serverHeaders",
"handleEndHeaders":"serverHeaders"} "handleEndHeaders":"serverHeaders"}
pmthds = {} def __init__(self):
self.pmthds = {}
self.plist = []
self.plist_all = []
@staticmethod @staticmethod
def getInstance(): def getInstance():
if ProxyPlugins._instance == None: if ProxyPlugins._instance is None:
ProxyPlugins._instance = ProxyPlugins() ProxyPlugins._instance = ProxyPlugins()
return ProxyPlugins._instance return ProxyPlugins._instance
def setPlugins(self, plugins): def addPlugin(self, p):
'''Set the plugins in use'''
for p in plugins:
self.addPlugin(p)
mitmf_logger.debug("[ProxyPlugins] Loaded {} plugin/s".format(len(self.plist)))
def addPlugin(self,p):
'''Load a plugin''' '''Load a plugin'''
self.plist.append(p) self.plist.append(p)
mitmf_logger.debug("[ProxyPlugins] Adding {} plugin".format(p.name)) mitmf_logger.debug("[ProxyPlugins] Adding {} plugin".format(p.name))
@ -77,12 +70,12 @@ class ProxyPlugins:
except KeyError: except KeyError:
self.pmthds[mthd] = [getattr(p,pmthd)] self.pmthds[mthd] = [getattr(p,pmthd)]
def removePlugin(self,p): def removePlugin(self, p):
'''Unload a plugin''' '''Unload a plugin'''
self.plist.remove(p) self.plist.remove(p)
mitmf_logger.debug("[ProxyPlugins] Removing {} plugin".format(p.name)) mitmf_logger.debug("[ProxyPlugins] Removing {} plugin".format(p.name))
for mthd,pmthd in self.mthdDict.iteritems(): for mthd,pmthd in self.mthdDict.iteritems():
self.pmthds[mthd].remove(p) self.pmthds[mthd].remove(getattr(p, pmthd))
def hook(self): def hook(self):
'''Magic to hook various function calls in sslstrip''' '''Magic to hook various function calls in sslstrip'''

View file

@ -419,14 +419,19 @@ class DNSChef(ConfigWatcher):
_instance = None _instance = None
version = "0.4" version = "0.4"
tcp = False def __init__(self):
ipv6 = False ConfigWatcher.__init__(self)
hsts = False
real_records = dict() self.tcp = False
nametodns = dict() self.ipv6 = False
server_address = "0.0.0.0" self.hsts = False
nameservers = ["8.8.8.8"] self.real_records = dict()
port = 53 self.nametodns = dict()
self.server_address = "0.0.0.0"
self.nameservers = ["8.8.8.8"]
self.port = 53
self.onConfigChange()
@staticmethod @staticmethod
def getInstance(): def getInstance():
@ -472,9 +477,6 @@ class DNSChef(ConfigWatcher):
self.hsts = True self.hsts = True
def start(self): def start(self):
self.onConfigChange()
self.startConfigWatch()
try: try:
if self.config['MITMf']['DNS']['tcp'].lower() == 'on': if self.config['MITMf']['DNS']['tcp'].lower() == 'on':
self.startTCP() self.startTCP()

View file

@ -60,7 +60,6 @@ class ServerConnection(HTTPClient):
self.urlMonitor = URLMonitor.getInstance() self.urlMonitor = URLMonitor.getInstance()
self.hsts = URLMonitor.getInstance().hsts self.hsts = URLMonitor.getInstance().hsts
self.app = URLMonitor.getInstance().app self.app = URLMonitor.getInstance().app
self.plugins = ProxyPlugins.getInstance()
self.isImageRequest = False self.isImageRequest = False
self.isCompressed = False self.isCompressed = False
self.contentLength = None self.contentLength = None
@ -108,7 +107,7 @@ class ServerConnection(HTTPClient):
def connectionMade(self): def connectionMade(self):
mitmf_logger.debug("[ServerConnection] HTTP connection made.") mitmf_logger.debug("[ServerConnection] HTTP connection made.")
self.plugins.hook() ProxyPlugins.getInstance().hook()
self.sendRequest() self.sendRequest()
self.sendHeaders() self.sendHeaders()
@ -117,7 +116,7 @@ class ServerConnection(HTTPClient):
def handleStatus(self, version, code, message): def handleStatus(self, version, code, message):
values = self.plugins.hook() values = ProxyPlugins.getInstance().hook()
version = values['version'] version = values['version']
code = values['code'] code = values['code']
@ -164,7 +163,7 @@ class ServerConnection(HTTPClient):
if self.length == 0: if self.length == 0:
self.shutdown() self.shutdown()
self.plugins.hook() ProxyPlugins.getInstance().hook()
if logging.getLevelName(mitmf_logger.getEffectiveLevel()) == "DEBUG": if logging.getLevelName(mitmf_logger.getEffectiveLevel()) == "DEBUG":
for header, value in self.client.headers.iteritems(): for header, value in self.client.headers.iteritems():
@ -191,7 +190,7 @@ class ServerConnection(HTTPClient):
data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read()
data = self.replaceSecureLinks(data) data = self.replaceSecureLinks(data)
data = self.plugins.hook()['data'] data = ProxyPlugins.getInstance().hook()['data']
mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data)))

View file

@ -18,12 +18,6 @@
# USA # USA
# #
"""
[enabled | disabled] by @xtr4nge
"""
import argparse import argparse
import sys import sys
import os import os
@ -35,17 +29,9 @@ from twisted.internet import reactor
from core.sslstrip.CookieCleaner import CookieCleaner from core.sslstrip.CookieCleaner import CookieCleaner
from core.sergioproxy.ProxyPlugins import ProxyPlugins from core.sergioproxy.ProxyPlugins import ProxyPlugins
from core.utils import Banners, SystemConfig, shutdown from core.utils import Banners, SystemConfig, shutdown
from core.mitmfapi import mitmfapi
from plugins import * from plugins import *
# @xtr4nge
import multiprocessing, time, signal
from flask import Flask
from configobj import ConfigObj
import json
# @xtr4nge
pluginStatus = ConfigObj("config/plugins.conf")
Banners().printBanner() Banners().printBanner()
if os.geteuid() != 0: if os.geteuid() != 0:
@ -81,6 +67,8 @@ plugins = []
try: try:
for p in plugin_classes: for p in plugin_classes:
plugins.append(p()) plugins.append(p())
ProxyPlugins.getInstance().plist_all = plugins
except Exception as e: except Exception as e:
print "[-] Failed to load plugin class {}: {}".format(p, e) print "[-] Failed to load plugin class {}: {}".format(p, e)
@ -143,10 +131,6 @@ for p in plugins:
#load only the plugins that have been called at the command line #load only the plugins that have been called at the command line
if vars(args)[p.optname] is True: if vars(args)[p.optname] is True:
# @xtr4nge
pluginStatus['plugins'][p.optname]['status'] = "enabled"
pluginStatus.write()
print "|_ {} v{}".format(p.name, p.version) print "|_ {} v{}".format(p.name, p.version)
if p.tree_info: if p.tree_info:
for line in xrange(0, len(p.tree_info)): for line in xrange(0, len(p.tree_info)):
@ -175,13 +159,14 @@ reactor.listenTCP(args.listen, strippingFactory)
for p in ProxyPlugins.getInstance().plist: for p in ProxyPlugins.getInstance().plist:
p.pluginReactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it p.pluginReactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it
p.startConfigWatch()
if hasattr(p, 'startThread'): if hasattr(p, 'startThread'):
t = threading.Thread(name='{}-Thread'.format(p.name), target=p.startThread) t = threading.Thread(name='{}-Thread'.format(p.name), target=p.startThread)
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
mitmfapi().start()
print "|" print "|"
print "|_ Sergio-Proxy v{} online".format(sergio_version) print "|_ Sergio-Proxy v{} online".format(sergio_version)
print "|_ SSLstrip v{} by Moxie Marlinspike online".format(sslstrip_version) print "|_ SSLstrip v{} by Moxie Marlinspike online".format(sslstrip_version)
@ -206,77 +191,8 @@ from core.servers.smb.SMBserver import SMBserver
print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver) print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver)
SMBserver.getInstance().start() SMBserver.getInstance().start()
'''
#start the reactor #start the reactor
reactor.run() reactor.run()
print "\n" print "\n"
shutdown() shutdown()
'''
# ------------------------------------
# @xtr4nge [enabled | disabled]
# ------------------------------------
app = Flask(__name__)
@app.route("/getPlugins")
def getPlugins():
# Lists all the plugins supporting [enabled|disabled] (check: config/plugins.conf)
# example: http://127.0.0.1:9090/getPlugins
pluginList = {"cachekill", "screen", "browserprofiler", "appoison", "replace", "smbtrap", "upsidedownternet"}
data = {}
for item in pluginList:
data[item] = [pluginStatus['plugins'][item]['status']]
return json.dumps(data)
@app.route("/getPluginStatus/<plugin>")
def getPluginStatus(plugin):
# example: http://127.0.0.1:9090/getPluginStatus/cachekill
return pluginStatus['plugins'][plugin]['status']
@app.route("/setPluginStatus/<plugin>/<status>")
def setPluginStatus(plugin, status):
# example: http://127.0.0.1:9090/setPluginStatus/cachekill/1 # enabled
# example: http://127.0.0.1:9090/setPluginStatus/cachekill/0 # disabled
if status == "1":
pluginStatus['plugins'][plugin]['status'] = "enabled"
pluginStatus.write()
elif status == "0":
pluginStatus['plugins'][plugin]['status'] = "disabled"
pluginStatus.write()
return getPluginStatus(plugin)
# @xtr4nge
def startFlask():
app.run(host='127.0.0.1', port=9090)
# @xtr4nge
def startCore():
#start the reactor
reactor.run()
# @xtr4nge
try:
pool = {}
pool[0] = multiprocessing.Process(name="core", target=startCore)
pool[1] = multiprocessing.Process(name="api", target=startFlask)
pool[0].start()
pool[1].start()
while True:
pass
except KeyboardInterrupt:
shutdown()
pool[0].terminate()
pool[1].terminate()
except Exception as e:
print e
shutdown()
pool[0].terminate()
pool[1].terminate()
finally:
print "bye ;)"

View file

@ -34,8 +34,6 @@ from datetime import date
from plugins.plugin import Plugin from plugins.plugin import Plugin
from core.sslstrip.URLMonitor import URLMonitor from core.sslstrip.URLMonitor import URLMonitor
from configobj import ConfigObj
mitmf_logger = logging.getLogger("mitmf") mitmf_logger = logging.getLogger("mitmf")
class AppCachePlugin(Plugin): class AppCachePlugin(Plugin):
@ -45,14 +43,6 @@ class AppCachePlugin(Plugin):
version = "0.3" version = "0.3"
has_opts = False has_opts = False
# @xtr4nge
def getStatus(self):
self.pluginStatus = ConfigObj("config/plugins.conf")
if self.pluginStatus['plugins'][self.optname]['status'] == "enabled":
return True
else:
return False
def initialize(self, options): def initialize(self, options):
self.options = options self.options = options
self.mass_poisoned_browsers = [] self.mass_poisoned_browsers = []
@ -61,7 +51,6 @@ class AppCachePlugin(Plugin):
self.urlMonitor.setAppCachePoisoning() self.urlMonitor.setAppCachePoisoning()
def serverResponse(self, response, request, data): def serverResponse(self, response, request, data):
if self.getStatus():
#This code was literally copied + pasted from Koto's sslstrip fork, def need to clean this up in the near future #This code was literally copied + pasted from Koto's sslstrip fork, def need to clean this up in the near future
self.app_config = self.config['AppCachePoison'] # so we reload the config on each request self.app_config = self.config['AppCachePoison'] # so we reload the config on each request

View file

@ -30,8 +30,6 @@ from pprint import pformat
from plugins.plugin import Plugin from plugins.plugin import Plugin
from plugins.Inject import Inject from plugins.Inject import Inject
from configobj import ConfigObj
mitmf_logger = logging.getLogger("mitmf") mitmf_logger = logging.getLogger("mitmf")
class BrowserProfiler(Inject, Plugin): class BrowserProfiler(Inject, Plugin):
@ -41,14 +39,6 @@ class BrowserProfiler(Inject, Plugin):
version = "0.3" version = "0.3"
has_opts = False has_opts = False
# @xtr4nge
def getStatus(self):
self.pluginStatus = ConfigObj("config/plugins.conf")
if self.pluginStatus['plugins'][self.optname]['status'] == "enabled":
return True
else:
return False
def initialize(self, options): def initialize(self, options):
self.output = {} # so other plugins can access the results self.output = {} # so other plugins can access the results
@ -63,8 +53,6 @@ class BrowserProfiler(Inject, Plugin):
return d return d
def clientRequest(self, request): def clientRequest(self, request):
if self.getStatus():
#Handle the plugin output
if 'clientprfl' in request.uri: if 'clientprfl' in request.uri:
request.printPostData = False request.printPostData = False

View file

@ -27,8 +27,6 @@
import logging import logging
from plugins.plugin import Plugin from plugins.plugin import Plugin
from configobj import ConfigObj
mitmf_logger = logging.getLogger("mitmf") mitmf_logger = logging.getLogger("mitmf")
class CacheKill(Plugin): class CacheKill(Plugin):
@ -37,20 +35,11 @@ class CacheKill(Plugin):
desc = "Kills page caching by modifying headers" desc = "Kills page caching by modifying headers"
version = "0.1" version = "0.1"
# @xtr4nge
def getStatus(self):
self.pluginStatus = ConfigObj("config/plugins.conf")
if self.pluginStatus['plugins'][self.optname]['status'] == "enabled":
return True
else:
return False
def initialize(self, options): def initialize(self, options):
self.bad_headers = ['if-none-match', 'if-modified-since'] self.bad_headers = ['if-none-match', 'if-modified-since']
def serverHeaders(self, response, request): def serverHeaders(self, response, request):
'''Handles all response headers''' '''Handles all response headers'''
if self.getStatus():
response.headers['Expires'] = "0" response.headers['Expires'] = "0"
response.headers['Cache-Control'] = "no-cache" response.headers['Cache-Control'] = "no-cache"

View file

@ -33,8 +33,6 @@ from plugins.plugin import Plugin
from plugins.CacheKill import CacheKill from plugins.CacheKill import CacheKill
from core.sergioproxy.ProxyPlugins import ProxyPlugins from core.sergioproxy.ProxyPlugins import ProxyPlugins
from configobj import ConfigObj
mitmf_logger = logging.getLogger("mitmf") mitmf_logger = logging.getLogger("mitmf")
class Replace(Plugin): class Replace(Plugin):
@ -44,14 +42,6 @@ class Replace(Plugin):
version = "0.2" version = "0.2"
has_opts = False has_opts = False
# @xtr4nge
def getStatus(self):
self.pluginStatus = ConfigObj("config/plugins.conf")
if self.pluginStatus['plugins'][self.optname]['status'] == "enabled":
return True
else:
return False
def initialize(self, options): def initialize(self, options):
self.options = options self.options = options
@ -60,12 +50,10 @@ class Replace(Plugin):
self.mime = "text/html" self.mime = "text/html"
def serverResponse(self, response, request, data): def serverResponse(self, response, request, data):
if self.getStatus():
ip, hn, mime = self._get_req_info(response) ip, hn, mime = self._get_req_info(response)
if self._should_replace(ip, hn, mime): if self._should_replace(ip, hn, mime):
# Did the user provide us with a regex file?
for rulename, regexs in self.config['Replace'].iteritems(): for rulename, regexs in self.config['Replace'].iteritems():
for regex1,regex2 in regexs.iteritems(): for regex1,regex2 in regexs.iteritems():
if re.search(regex1, data): if re.search(regex1, data):

View file

@ -10,8 +10,6 @@ import string
from plugins.plugin import Plugin from plugins.plugin import Plugin
from core.utils import SystemConfig from core.utils import SystemConfig
from configobj import ConfigObj
mitmf_logger = logging.getLogger("mitmf") mitmf_logger = logging.getLogger("mitmf")
class SMBTrap(Plugin): class SMBTrap(Plugin):
@ -21,19 +19,10 @@ class SMBTrap(Plugin):
version = "1.0" version = "1.0"
has_opts = False has_opts = False
# @xtr4nge
def getStatus(self):
self.pluginStatus = ConfigObj("config/plugins.conf")
if self.pluginStatus['plugins'][self.optname]['status'] == "enabled":
return True
else:
return False
def initialize(self, options): def initialize(self, options):
self.ourip = SystemConfig.getIP(options.interface) self.ourip = SystemConfig.getIP(options.interface)
def serverResponseStatus(self, request, version, code, message): def serverResponseStatus(self, request, version, code, message):
if self.getStatus():
return {"request": request, "version": version, "code": 302, "message": "Found"} return {"request": request, "version": version, "code": 302, "message": "Found"}
def serverHeaders(self, response, request): def serverHeaders(self, response, request):

View file

@ -33,8 +33,6 @@ from datetime import datetime
from plugins.Inject import Inject from plugins.Inject import Inject
from plugins.plugin import Plugin from plugins.plugin import Plugin
from configobj import ConfigObj
mitmf_logger = logging.getLogger('mitmf') mitmf_logger = logging.getLogger('mitmf')
class ScreenShotter(Inject, Plugin): class ScreenShotter(Inject, Plugin):
@ -44,21 +42,12 @@ class ScreenShotter(Inject, Plugin):
ver = '0.1' ver = '0.1'
has_opts = True has_opts = True
# @xtr4nge
def getStatus(self):
self.pluginStatus = ConfigObj("config/plugins.conf")
if self.pluginStatus['plugins'][self.optname]['status'] == "enabled":
return True
else:
return False
def initialize(self, options): def initialize(self, options):
self.interval = 10 or options.interval self.interval = 10 or options.interval
Inject.initialize(self, options) Inject.initialize(self, options)
self.html_payload = self.get_payload() self.html_payload = self.get_payload()
def clientRequest(self, request): def clientRequest(self, request):
if self.getStatus():
if 'saveshot' in request.uri: if 'saveshot' in request.uri:
request.printPostData = False request.printPostData = False
client = request.client.getClientIP() client = request.client.getClientIP()

View file

@ -29,8 +29,6 @@ from cStringIO import StringIO
from plugins.plugin import Plugin from plugins.plugin import Plugin
from PIL import Image, ImageFile from PIL import Image, ImageFile
from configobj import ConfigObj
mitmf_logger = logging.getLogger("mitmf") mitmf_logger = logging.getLogger("mitmf")
class Upsidedownternet(Plugin): class Upsidedownternet(Plugin):
@ -40,14 +38,6 @@ class Upsidedownternet(Plugin):
version = "0.1" version = "0.1"
has_opts = False has_opts = False
# @xtr4nge
def getStatus(self):
self.pluginStatus = ConfigObj("config/plugins.conf")
if self.pluginStatus['plugins'][self.optname]['status'] == "enabled":
return True
else:
return False
def initialize(self, options): def initialize(self, options):
globals()['Image'] = Image globals()['Image'] = Image
globals()['ImageFile'] = ImageFile globals()['ImageFile'] = ImageFile
@ -62,7 +52,6 @@ class Upsidedownternet(Plugin):
self.imageType = response.headers['content-type'].split('/')[1].upper() self.imageType = response.headers['content-type'].split('/')[1].upper()
def serverResponse(self, response, request, data): def serverResponse(self, response, request, data):
if self.getStatus():
try: try:
isImage = getattr(request, 'isImage') isImage = getattr(request, 'isImage')
except AttributeError: except AttributeError: