diff --git a/.gitignore b/.gitignore
index 1187508..95fd390 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ cache/
config/
*.bak
*.bak.js
+*.dev
diff --git a/README.md b/README.md
index a21d00d..06190dc 100644
--- a/README.md
+++ b/README.md
@@ -36,8 +36,11 @@ By default, House binds to http://127.0.0.1:8000.
To get an overview of House capabilities, you can visit [user's manual](https://github.com/nccgroup/house/wiki/Overview) for details.
## News
-- House now runs on **python3**, as python2 will retire in 2019.
-- Added Monitor tab, inspired by [Inspeckage](https://github.com/ac-pm/Inspeckage).
+- Added dynamic dex/jar hooking, House now can hook functions in dynamically loaded dex/jar files
+
+- Added Mini Script option for Hooks
+
+- ClassLoader Enum
## Example Usage
@@ -89,6 +92,12 @@ following command: `frida-ps -U`.
- *Scripts rendering and Function Tracing*

+- *Hooks for functions in dynamically loaded dex/jar files*
+ 
+
+- *Mini Script option*
+ 
+
- *History Scripts management*

diff --git a/app.py b/app.py
index ec46227..b2a24c8 100644
--- a/app.py
+++ b/app.py
@@ -124,7 +124,6 @@ def hook():
class_name = str(request.form.get('classname'))
method_name = str(request.form.get('methodname'))
if (method_name != 'None') & (class_name != 'None'):
- # TODO: use jinja to render script dynamically
hook_dict = {"classname": class_name, "methodname": method_name}
house_global.hooks_list.append(hook_dict)
update_conf()
diff --git a/gifs/dyload.gif b/gifs/dyload.gif
new file mode 100644
index 0000000..78989d7
Binary files /dev/null and b/gifs/dyload.gif differ
diff --git a/gifs/dyload_mini.gif b/gifs/dyload_mini.gif
new file mode 100644
index 0000000..8d23914
Binary files /dev/null and b/gifs/dyload_mini.gif differ
diff --git a/houseStatic.py b/houseStatic.py
index c223c24..00685d5 100644
--- a/houseStatic.py
+++ b/houseStatic.py
@@ -21,6 +21,7 @@
# SOFTWARE.
import colored
+from enum import Enum
from colored import stylize
import frida
@@ -40,4 +41,9 @@
linebreak = "h0us3l1nebr3ak"
codeblock = "h0us3bl0ck"
-delimiter = "h4oar3ud0ing"
\ No newline at end of file
+delimiter = "h4oar3ud0ing"
+
+class dynamicHookOption(Enum):
+ only_new = "new"
+ only_existing = "existing"
+ both = "all"
\ No newline at end of file
diff --git a/houseUtil.py b/houseUtil.py
index e02eb4a..bc18c06 100644
--- a/houseUtil.py
+++ b/houseUtil.py
@@ -21,32 +21,35 @@
# SOFTWARE.
from houseStatic import *
-from houseGlobal import house_global,socketio, random_token
+from houseGlobal import house_global, socketio, random_token
# from plugin import log_r_value
# from houseGlobal import env_html, enum_html, intercepts_html, preload_html, hooks_html, monitor_html
from _frida import ProcessNotFoundError
import traceback, IPython
+
def init_conf():
if not (os.path.exists('config')):
os.makedirs('./config')
if not (os.path.isfile("./config/hook_conf.json")):
- with open("./config/hook_conf.json",'w') as f:
+ with open("./config/hook_conf.json", 'w') as f:
f.write('{"packagename": "", "hooks_list": []}')
if not (os.path.isfile("./config/enum_conf.json")):
- with open("./config/enum_conf.json",'w') as f:
+ with open("./config/enum_conf.json", 'w') as f:
f.write('{"packagename": "", "enum_option": "", "class_pattern": "", "class_to_find": ""}')
if not (os.path.isfile("./config/intercept_conf.json")):
- with open("./config/intercept_conf.json",'w') as f:
+ with open("./config/intercept_conf.json", 'w') as f:
f.write('{"classname": "", "packagename": "", "methodname": "", "overloadIndex": 0}')
if not (os.path.isfile("./config/env_conf.json")):
- with open("./config/env_conf.json",'w') as f:
- f.write('{"filesDirectory": "", "cacheDirectory": "", "externalCacheDirectory": "", "codeCacheDirectory": "", "packageCodePath": ""}')
+ with open("./config/env_conf.json", 'w') as f:
+ f.write(
+ '{"filesDirectory": "", "cacheDirectory": "", "externalCacheDirectory": "", "codeCacheDirectory": "", "packageCodePath": ""}')
if not (os.path.isfile("./config/monitor_conf.json")):
- with open("./config/monitor_conf.json",'w') as f:
- f.write('{"SWITCH_FILEIO": 0, "SWITCH_HTTP": 0, "SWITCH_MISC": 0, "SWITCH_WEBVIEW": 0, "SWITCH_SQL": 0, "SWITCH_IPC": 0}')
+ with open("./config/monitor_conf.json", 'w') as f:
+ f.write(
+ '{"SWITCH_FILEIO": 0, "SWITCH_HTTP": 0, "SWITCH_MISC": 0, "SWITCH_WEBVIEW": 0, "SWITCH_SQL": 0, "SWITCH_IPC": 0}')
if not (os.path.isfile("./config/preload_conf.json")):
- with open("./config/preload_conf.json",'w') as f:
+ with open("./config/preload_conf.json", 'w') as f:
f.write('{"PRELOAD_STETHO": 0, "PRELOAD_SSLSTRIP": 1, "PRELOAD_SETPROXY": 0}')
@@ -63,26 +66,25 @@ def init_cache():
os.makedirs('./cache/log')
if not (os.path.isfile("./cache/current/enum_script.js")):
- with open('./cache/current/enum_script.js','w') as f:
+ with open('./cache/current/enum_script.js', 'w') as f:
f.write('')
if not (os.path.isfile("./cache/current/hook_script.js")):
- with open('./cache/current/hook_script.js','w') as f:
+ with open('./cache/current/hook_script.js', 'w') as f:
f.write('')
if not (os.path.isfile("./cache/log/message.json")):
- with open("./cache/log/message.json",'w') as f:
+ with open("./cache/log/message.json", 'w') as f:
f.write('')
if not (os.path.isfile("./cache/log/monitor.json")):
- with open("./cache/log/monitor.json",'w') as f:
+ with open("./cache/log/monitor.json", 'w') as f:
f.write('')
-
-
def make_tree(path, option):
tree = dict(name=os.path.basename(path), children=[])
- try: lst = os.listdir(path)
+ try:
+ lst = os.listdir(path)
except OSError:
- pass #ignore errors
+ pass # ignore errors
else:
for name in lst:
fn = os.path.join(path, name)
@@ -91,7 +93,7 @@ def make_tree(path, option):
def get_apk_path():
- with open("./config/env_conf.json",'r') as f:
+ with open("./config/env_conf.json", 'r') as f:
house_global.env_conf = f.read()
try:
j_env_conf = json.loads(house_global.env_conf)
@@ -100,8 +102,9 @@ def get_apk_path():
return None
raise e
+
def get_application_name():
- with open("./config/env_conf.json",'r') as f:
+ with open("./config/env_conf.json", 'r') as f:
house_global.env_conf = f.read()
try:
j_env_conf = json.loads(house_global.env_conf)
@@ -110,6 +113,7 @@ def get_application_name():
return None
raise e
+
def update_conf():
house_global.hook_conf["packagename"] = house_global.packagename
@@ -120,22 +124,23 @@ def update_conf():
house_global.enum_conf["enum_option"] = house_global.enum_option
house_global.hook_conf['hooks_list'] = house_global.hooks_list
- with open('./config/hook_conf.json','w') as f:
+ with open('./config/hook_conf.json', 'w') as f:
# IPython.embed()
f.write(json.dumps(house_global.hook_conf))
- with open('./config/enum_conf.json','w') as f:
+ with open('./config/enum_conf.json', 'w') as f:
f.write(json.dumps(house_global.enum_conf))
- with open('./config/intercept_conf.json','w') as f:
+ with open('./config/intercept_conf.json', 'w') as f:
# print stylize("[+] Updating intercept_conf with {}".format(json.dumps(house_global.inspect_conf)),Info)
f.write(json.dumps(house_global.inspect_conf))
- with open('./config/monitor_conf.json','w') as f:
+ with open('./config/monitor_conf.json', 'w') as f:
f.write(json.dumps(house_global.monitor_conf))
- with open('./config/preload_conf.json','w') as f:
+ with open('./config/preload_conf.json', 'w') as f:
f.write(json.dumps(house_global.preload_conf))
+
def init_settings():
try:
house_global.packagename = house_global.hook_conf["packagename"]
@@ -144,71 +149,78 @@ def init_settings():
except Exception as e:
pass
+
def cache_script(option, script):
if option == "enum_cache":
- with open('./cache/current/enum_script.js','w') as f:
+ with open('./cache/current/enum_script.js', 'w') as f:
f.write(script)
elif option == "env_cache":
- with open('./cache/current/env_script.js','w') as f:
+ with open('./cache/current/env_script.js', 'w') as f:
f.write(script)
elif option == "hooks_cache":
- with open('./cache/current/hook_script.js','w') as f:
+ with open('./cache/current/hook_script.js', 'w') as f:
f.write(script)
elif option == "hooks_cache_mini":
- with open('./cache/current/hook_script_mini.js','w') as f:
+ with open('./cache/current/hook_script_mini.js', 'w') as f:
f.write(script)
elif option == "intercept_cache":
- with open('./cache/current/intercept_script.js','w') as f:
+ with open('./cache/current/intercept_script.js', 'w') as f:
f.write(script)
elif option == "monitor_cache":
- with open('./cache/current/monitor_script.js','w') as f:
+ with open('./cache/current/monitor_script.js', 'w') as f:
f.write(script)
elif option == "preload_cache":
- with open('./cache/current/preload_script.js','w') as f:
+ with open('./cache/current/preload_script.js', 'w') as f:
f.write(script)
else:
- print (stylize("[!] Invalid cache script type", Error))
+ print(stylize("[!] Invalid cache script type", Error))
+
def clear_hook_msg():
house_global.messages = []
socketio.emit("clear_hook_msg")
+
def cache_inspect_html():
- with open('./config/inspect_cache.html','w') as f:
+ with open('./config/inspect_cache.html', 'w') as f:
f.write(house_global.inspect_result)
+
def checkok():
if (house_global.packagename != "") & (house_global.hooks_list != []):
return True
else:
return False
+
def getPackage():
return house_global.hook_conf['packagename']
+
def setPackage(pkgname):
house_global.packagename = pkgname
house_global.inspect_conf["classname"] = ""
house_global.inspect_conf["methodname"] = ""
house_global.inspect_conf["overloadIndex"] = 0
- with open('./config/inspect_cache.html','w') as f:
+ with open('./config/inspect_cache.html', 'w') as f:
f.write(" ")
update_conf()
-
+
+
def setDevice(id):
house_global.device = house_global.device_manager.get_device(id)
- print (stylize("[+]Changing Device with id {}".format(id), MightBeImportant))
+ print(stylize("[+]Changing Device with id {}".format(id), MightBeImportant))
try:
socketio.emit('show_selected_device',
- {'device_list': json.dumps(house_global.device_dict), 'selection': str(house_global.device.id)},
- namespace='/eventBus')
+ {'device_list': json.dumps(house_global.device_dict), 'selection': str(house_global.device.id)},
+ namespace='/eventBus')
except Exception as e:
raise e
-
+
def getDevice():
try:
- print (stylize("[+] Trying to get device..", Info))
+ print(stylize("[+] Trying to get device..", Info))
house_global.device_dict = {}
house_global.device_manager = frida.get_device_manager()
device_list = house_global.device_manager.enumerate_devices()
@@ -219,52 +231,54 @@ def getDevice():
remote_device_list.append(dv)
if len(remote_device_list) == 1:
house_global.device = remote_device_list[0]
- socketio.emit('update_device',
- {'data': cgi.escape(str(house_global.device))},
- namespace='/eventBus')
+ socketio.emit('update_device',
+ {'data': cgi.escape(str(house_global.device))},
+ namespace='/eventBus')
elif len(remote_device_list) > 1:
for dv in remote_device_list:
house_global.device_dict[str(dv.id)] = str(dv)
# Interact with user to select device
if house_global.device == None:
socketio.emit('select_device',
- {'device_list': json.dumps(house_global.device_dict)},
- namespace='/eventBus')
+ {'device_list': json.dumps(house_global.device_dict)},
+ namespace='/eventBus')
else:
socketio.emit('show_selected_device',
- {'device_list': json.dumps(house_global.device_dict), 'selection': str(house_global.device.id)},
- namespace='/eventBus')
+ {'device_list': json.dumps(house_global.device_dict),
+ 'selection': str(house_global.device.id)},
+ namespace='/eventBus')
else:
raise Exception("No device Found!")
# return str(house_global.device)
except Exception as e:
house_global.device = None
- socketio.emit('update_device',
- {'data': cgi.escape(str(house_global.device))},
- namespace='/eventBus')
- print (stylize(str(e), Error))
+ socketio.emit('update_device',
+ {'data': cgi.escape(str(house_global.device))},
+ namespace='/eventBus')
+ print(stylize(str(e), Error))
# raise e
-def onMonitorMessage(message,data):
+
+def onMonitorMessage(message, data):
house_global.onMessageException = ''
if message['type'] == 'send':
- if(message.get('payload') != None):
+ if (message.get('payload') != None):
monitor_log = str(message.get('payload'))
# monitor_log = u''.join(monitor_log).encode('utf-8').strip()
else:
monitor_log = "No message payload.."
elif message['type'] == 'error':
- if(message.get('description') != None):
+ if (message.get('description') != None):
house_global.onMessageException = cgi.escape(message.get('description'))
else:
house_global.onMessageException = 'No description'
- print (stylize("[!]Monitor Error: {}".format(house_global.onMessageException), Error))
+ print(stylize("[!]Monitor Error: {}".format(house_global.onMessageException), Error))
socketio.emit('new_error_message',
- {'data': "[!] {}".format(house_global.onMessageException)},
- namespace='/eventBus')
+ {'data': "[!] {}".format(house_global.onMessageException)},
+ namespace='/eventBus')
monitor_log = message.get('payload') if message.get('payload') else ''
-
+
j_monitor_log = json.loads(monitor_log)
mon_type = j_monitor_log.get("monitor_type")
@@ -272,71 +286,71 @@ def onMonitorMessage(message,data):
method = j_monitor_log.get("method_info")
retval = j_monitor_log.get("retval_dump")
if args != None:
- args = cgi.escape(args).replace(linebreak,'
')
+ args = cgi.escape(args).replace(linebreak, '
')
if method != None:
- method = cgi.escape(method).replace(linebreak,'
')
+ method = cgi.escape(method).replace(linebreak, '
')
if retval != None:
- retval = cgi.escape(retval).replace(linebreak,'
')
- monitor_entry = {"methodname":method,"args":args,"retval":retval}
+ retval = cgi.escape(retval).replace(linebreak, '
')
+ monitor_entry = {"methodname": method, "args": args, "retval": retval}
# "types" : ["fileIO", "HTTP", "WEBVIEW", "SQL", "IPC", "MISC", "IGNORE"]
if (mon_type != None) & (mon_type != "IGNORE"):
if mon_type == "fileIO":
- house_global.monitor_message['FILEIO'].insert(0,monitor_entry)
+ house_global.monitor_message['FILEIO'].insert(0, monitor_entry)
elif mon_type == "SHAREDPREFERENCES":
- house_global.monitor_message['SHAREDPREFERENCES'].insert(0,monitor_entry)
+ house_global.monitor_message['SHAREDPREFERENCES'].insert(0, monitor_entry)
elif mon_type == "HTTP":
- house_global.monitor_message['HTTP'].insert(0,monitor_entry)
+ house_global.monitor_message['HTTP'].insert(0, monitor_entry)
elif mon_type == "WEBVIEW":
- house_global.monitor_message['WEBVIEW'].insert(0,monitor_entry)
+ house_global.monitor_message['WEBVIEW'].insert(0, monitor_entry)
elif mon_type == "SQL":
- house_global.monitor_message['SQL'].insert(0,monitor_entry)
+ house_global.monitor_message['SQL'].insert(0, monitor_entry)
elif mon_type == "IPC":
- house_global.monitor_message['IPC'].insert(0,monitor_entry)
- else: # misc
+ house_global.monitor_message['IPC'].insert(0, monitor_entry)
+ else: # misc
mon_type = "MISC"
- house_global.monitor_message['MISC'].insert(0,monitor_entry)
+ house_global.monitor_message['MISC'].insert(0, monitor_entry)
# socketio.emit('update_monitor_message', {'mon_type': mon_type.upper(), 'monitor_message': house_global.monitor_message},namespace='/eventBus')
house_global.monitor_queue.add(mon_type.upper())
-def onMessage(message,data):
+def onMessage(message, data):
house_global.onMessageException = ''
if message['type'] == 'send':
- if(message.get('payload') != None):
+ if (message.get('payload') != None):
info = str(message.get('payload'))
# info = str(u''.join(info).encode('utf-8').strip())
else:
info = "No message payload.."
elif message['type'] == 'error':
- if(message.get('description') != None):
+ if (message.get('description') != None):
house_global.onMessageException = cgi.escape(message.get('description'))
else:
house_global.onMessageException = 'No description'
- print (stylize("[!]Error: {}".format(house_global.onMessageException), Error))
+ print(stylize("[!]Error: {}".format(house_global.onMessageException), Error))
socketio.emit('new_error_message',
- {'data': "[!] {}".format(house_global.onMessageException)},
- namespace='/eventBus')
+ {'data': "[!] {}".format(house_global.onMessageException)},
+ namespace='/eventBus')
info = message.get('payload') if message.get('payload') else ''
# IPython.embed()
if "t3llm3mor3ab0ut1t" in info:
- env_info = info.replace("t3llm3mor3ab0ut1t",'')
+ env_info = info.replace("t3llm3mor3ab0ut1t", '')
# print (env_info)
# IPython.embed()
j_env_info = json.loads(env_info)
if j_env_info.get("packageCodePath") != None:
- with open("./config/env_conf.json",'w') as f:
- json.dump(j_env_info,f)
+ with open("./config/env_conf.json", 'w') as f:
+ json.dump(j_env_info, f)
socketio.emit('update_env_info',
- {'data': env_info},
- namespace='/eventBus')
+ {'data': env_info},
+ namespace='/eventBus')
# env stuff
if "-hoo00ook-" in info:
- info = info.replace("-hoo00ook-",'')
-
+ info = info.replace("-hoo00ook-", '')
+
j_info = json.loads(info)
args = j_info.get("arg_dump")
method = j_info.get("method_info")
@@ -344,29 +358,29 @@ def onMessage(message,data):
# special_instruction = j_info.get("special_inst") # experimental
if args != None:
- args = cgi.escape(args).replace(linebreak,'
')
+ args = cgi.escape(args).replace(linebreak, '
')
if method != None:
- method = cgi.escape(method).replace(linebreak,'
')
+ method = cgi.escape(method).replace(linebreak, '
')
if retval != None:
- retval = cgi.escape(retval).replace(linebreak,'
')
+ retval = cgi.escape(retval).replace(linebreak, '
')
- info_dict = {"methodname":method,"args":args,"retval":retval}
+ info_dict = {"methodname": method, "args": args, "retval": retval}
- house_global.messages.insert(0,info_dict)
+ house_global.messages.insert(0, info_dict)
# if special_instruction != None: # experimental
# log_r_value(special_instruction)
socketio.emit('new_hook_message',
- {'data': json.dumps(info_dict)},
- namespace='/eventBus')
+ {'data': json.dumps(info_dict)},
+ namespace='/eventBus')
if "-enumMmMmMmMm-" in info:
- enum_msg = info.replace('undefined','').replace("-enumMmMmMmMm-",'')
+ enum_msg = info.replace('undefined', '').replace("-enumMmMmMmMm-", '')
house_global.enum_messages.insert(0, enum_msg)
- socketio.emit("update_enum_messages",namespace='/eventBus')
+ socketio.emit("update_enum_messages", namespace='/eventBus')
if "-t1m3f0rm1tm-" in info:
- intercept_msg = info.replace("-t1m3f0rm1tm-",'')
+ intercept_msg = info.replace("-t1m3f0rm1tm-", '')
if "-what1sth3t1m3n0w-" in intercept_msg:
house_global.new_intercept_msg = intercept_msg.split("-what1sth3t1m3n0w-")[0]
@@ -374,10 +388,12 @@ def onMessage(message,data):
else:
house_global.new_intercept_msg = intercept_msg
- socketio.emit('new_intercept', {'data': house_global.new_intercept_msg, 'time': house_global.new_intercept_time}, namespace='/eventBus')
+ socketio.emit('new_intercept',
+ {'data': house_global.new_intercept_msg, 'time': house_global.new_intercept_time},
+ namespace='/eventBus')
if "-whatisth1smeth0d-" in info:
- inspect_info = info.replace("-whatisth1smeth0d-",'')
+ inspect_info = info.replace("-whatisth1smeth0d-", '')
j_inspect = json.loads(inspect_info)
overload_info = j_inspect['methodInfo']
@@ -387,9 +403,10 @@ def onMessage(message,data):
inspect_class_name = house_global.inspect_conf["classname"]
inspect_method_name = house_global.inspect_conf["methodname"]
html_output = ""
-
+
if overload_count > 1:
- html_output = "
{}
{}