2

In developing a RESTful API for IDA >= 7.5 [https://github.com/sfinktah/idarest75], I have observed that a standard threaded webserver does not release it's socket when IDA terminates, unless it is run as a plugin. This behaviour may extend to all threading, I have not tested.

If the code below is simply loaded as a script (or pasted into the CLI) it will actually cause IDA to invisibly hang when IDA is exited, i.e. IDA appears to close but can actually found to be still running in Task Explorer -> Details.

Is there perhaps an IDA-specific atexit analog, as the builtin Python version certainly does not help.

Whilst the finished project is capable of running either as a plugin or a stand-alone script, the lack of any way to unload a python plugin requires keeping a reference to the class instance in order to call .term(), which is contra-indicated if one actually expects destruction to occur correctly.

Sample code (paste, exit IDA, observe running tasks).

def ida_issue():
    from http.server import BaseHTTPRequestHandler, HTTPServer
    from socketserver import ThreadingMixIn
    import threading
class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    allow_reuse_address = True

class Worker(threading.Thread):
    def __init__(self, host, port):
        threading.Thread.__init__(self)
        self.httpd = ThreadedHTTPServer((host, port), Handler)
        self.host = host
        self.port = port

    def run(self):
        self.httpd.serve_forever()

    def stop(self):
        self.httpd.shutdown()
        self.httpd.server_close()

class Master:
    def __init__(self):
        self.worker = Worker('127.0.0.1', 28612)
        self.worker.start()

def main():
    master = Master()
    return master

return main()

server = ida_issue()

How to re/unload [co-operative] IDAPython plugins.

class idarest_plugin_t(IdaRestConfiguration, ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_UNL
def run(self, *args):
    pass

def reload_idarest(): l = ida_loader.load_plugin(sys.modules['__plugins__idarest_plugin'].file) ida_load.run_plugin(l, 0) # pip install exectools (or ida_idaapi.require would probably suffice) unload('idarest_plugin') # reload plugin (or not) l = ida_loader.load_plugin(sys.modules['__plugins__idarest_plugin'].file)

Orwellophile
  • 320
  • 1
  • 9

1 Answers1

2

Some research into the atexit module provided a simple solution. Create a plugin purely to trigger the atexit handlers when it receives a term.

import atexit
import idc
import ida_idaapi

class ida_atexit(ida_idaapi.plugin_t): flags = ida_idaapi.PLUGIN_UNL comment = "atexit" help = "triggers atexit._run_exitfuncs() when ida halts plugins" wanted_name = "" wanted_hotkey = ""

def init(self):
    super(ida_atexit, self).__init__()
    return ida_idaapi.PLUGIN_KEEP

def run(*args):
    pass

def term(self):
    idc.msg('[ida_atexit::term] calling atexit._run_exitfuncs()\n')
    atexit._run_exitfuncs()

def PLUGIN_ENTRY(): globals()['instance'] = ida_atexit() return globals()['instance']

Orwellophile
  • 320
  • 1
  • 9