Add a lot of stuff
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
# ida-scripts
|
||||
Collection of IDA Pro/Hex-Rays scripts
|
||||
# IDA configs
|
||||
Collection of my IDA Pro/Hex-Rays scripts and plugins
|
||||
|
||||
Tested on IDA7.0 since I am too broke to afford IDA7.1
|
||||
|
||||
I didn't write the ret-sync stuff, it's just a port for the new Python plugin API.
|
||||
|
||||
1448
cfg/ida.cfg
Normal file
1448
cfg/ida.cfg
Normal file
File diff suppressed because it is too large
Load Diff
1679
cfg/idagui.cfg
Normal file
1679
cfg/idagui.cfg
Normal file
File diff suppressed because it is too large
Load Diff
114
ida.clr
Normal file
114
ida.clr
Normal file
@@ -0,0 +1,114 @@
|
||||
[DISASM]
|
||||
000000 //
|
||||
aaaaaa //Default color
|
||||
f3c5ff //Regular comment
|
||||
7e6082 //Repeatable comment
|
||||
666666 //Automatic comment
|
||||
ffffff //Instruction
|
||||
b9ebeb //Dummy Data Name
|
||||
b9ebeb //Regular Data Name
|
||||
bbecff //Demangled Name
|
||||
c0c0c0 //Punctuation
|
||||
00d269 //Char constant in instruction
|
||||
00ff00 //String constant in instruction
|
||||
3250d2 //Numeric constant in instruction
|
||||
4646ff //Void operand
|
||||
7faaff //Code reference
|
||||
617c7c //Data reference
|
||||
3250d2 //Code reference to tail byte
|
||||
008080 //Data reference to tail byte
|
||||
3734ff //Error or problem
|
||||
c0c0c0 //Line prefix
|
||||
595959 //Binary line prefix bytes
|
||||
f3c5ff //Extra line
|
||||
ffaaff //Alternative operand
|
||||
00d2ff //Hidden name
|
||||
ffff00 //Library function name
|
||||
0080ff //Local variable name
|
||||
00d2ff //Dummy code name
|
||||
00d69d //Assembler directive
|
||||
7e07df //Macro
|
||||
00d269 //String constant in data directive
|
||||
00f379 //Char constant in data directive
|
||||
3250d2 //Numeric constant in data directive
|
||||
ababab //Keywords
|
||||
adad73 //Register name
|
||||
fd5aff //Imported name
|
||||
7fffff //Segment name
|
||||
00ffaa //Dummy unknown name
|
||||
00d2ff //Regular code name
|
||||
ffaaff //Regular unknown name
|
||||
00ffff //Collapsed line
|
||||
000000 //Max color number
|
||||
2d2d2d //Line prefix: library function
|
||||
4b4b4b //Line prefix: regular function
|
||||
ffff00 //Line prefix: instruction
|
||||
666666 //Line prefix: data
|
||||
0000aa //Line prefix: unexplored
|
||||
617c7c //Line prefix: externs
|
||||
009d9d //Line prefix: current item
|
||||
ff55ff //Line prefix: current line
|
||||
000000 //Punctuation
|
||||
00aaff //Opcode bytes
|
||||
000000 //Manual operand
|
||||
[NAVBAR]
|
||||
429cc8 //Library function
|
||||
8a7a43 //Regular function
|
||||
319659 //Instruction
|
||||
a1c8c8 //Data item
|
||||
555555 //Unexplored
|
||||
c86ac8 //External symbol
|
||||
2b2bca //Errors
|
||||
4a4a4a //Gaps
|
||||
41ffa3 //Cursor
|
||||
5c37ff //Address
|
||||
[DEBUG]
|
||||
ffd060 //Current IP
|
||||
ffa0a0 //Current IP (+ enabled breakpoint)
|
||||
408020 //Current IP (+ disabled breakpoint)
|
||||
2d2d2d //Default background
|
||||
000076 //Address (+ enabled breakpoint)
|
||||
00ff00 //Address (+ disabled breakpoint)
|
||||
004080 //Current IP (+ unavailable breakpoint)
|
||||
0080ff //Address (+ unavailable breakpoint)
|
||||
000000 //Registers
|
||||
ff0000 //Registers (changed)
|
||||
800080 //Registers (edited)
|
||||
[ARROW]
|
||||
34466c //Jump in current function
|
||||
dede00 //Jump external to function
|
||||
00aaff //Jump under the cursor
|
||||
008000 //Jump target
|
||||
ff4040 //Register target
|
||||
[GRAPH]
|
||||
787878 //Top color
|
||||
4c4c4c //Bottom color
|
||||
54585e //Normal title
|
||||
f5f5f5 //Selected title
|
||||
989faa //Current title
|
||||
0400ff //Group frame
|
||||
242424 //Node shadow
|
||||
003900 //Highlight color 1
|
||||
00006d //Highlight color 2
|
||||
0000ff //Foreign node
|
||||
cb4300 //Normal edge
|
||||
00b400 //Yes edge
|
||||
0000bc //No edge
|
||||
ffaaaa //Highlighted edge
|
||||
52a7c8 //Current edge
|
||||
[MISC]
|
||||
d4d4d4 //Message text
|
||||
212121 //Message background
|
||||
404080 //Patched bytes
|
||||
0080ff //Unsaved changes
|
||||
[OTHER]
|
||||
4f6865 //Highlight color
|
||||
3b4444 //Hint color
|
||||
[SYNTAX]
|
||||
ff0000 0 0 //Keyword 1
|
||||
800080 0 0 //Keyword 2
|
||||
0000ff 0 0 //Keyword 3
|
||||
00008b 0 0 //String
|
||||
006400 0 1 //Comment
|
||||
ff0000 1 0 //Preprocessor
|
||||
8b8b00 1 0 //Number
|
||||
BIN
plugins/HexRaysCodeXplorer.dll
Normal file
BIN
plugins/HexRaysCodeXplorer.dll
Normal file
Binary file not shown.
BIN
plugins/HexRaysCodeXplorer64.dll
Normal file
BIN
plugins/HexRaysCodeXplorer64.dll
Normal file
Binary file not shown.
BIN
plugins/HexraysInvertIf32.dll
Normal file
BIN
plugins/HexraysInvertIf32.dll
Normal file
Binary file not shown.
BIN
plugins/HexraysInvertIf64.dll
Normal file
BIN
plugins/HexraysInvertIf64.dll
Normal file
Binary file not shown.
BIN
plugins/IDASkins.dll
Normal file
BIN
plugins/IDASkins.dll
Normal file
Binary file not shown.
BIN
plugins/IDASkins64.dll
Normal file
BIN
plugins/IDASkins64.dll
Normal file
Binary file not shown.
BIN
plugins/IDA_ClassInformer_PlugIn.dll
Normal file
BIN
plugins/IDA_ClassInformer_PlugIn.dll
Normal file
Binary file not shown.
BIN
plugins/IDA_ClassInformer_PlugIn64.dll
Normal file
BIN
plugins/IDA_ClassInformer_PlugIn64.dll
Normal file
Binary file not shown.
BIN
plugins/IDA_FunctionStringAssociate_PlugIn32.dll
Normal file
BIN
plugins/IDA_FunctionStringAssociate_PlugIn32.dll
Normal file
Binary file not shown.
BIN
plugins/IDA_FunctionStringAssociate_PlugIn64.dll
Normal file
BIN
plugins/IDA_FunctionStringAssociate_PlugIn64.dll
Normal file
Binary file not shown.
@@ -97,15 +97,32 @@ class hexrays_callback_info(object):
|
||||
|
||||
return 0
|
||||
|
||||
if idaapi.init_hexrays_plugin():
|
||||
i = hexrays_callback_info()
|
||||
idaapi.register_action(
|
||||
idaapi.action_desc_t(
|
||||
force_width_actname,
|
||||
"Force lvar width",
|
||||
force_width_action_handler_t(i),
|
||||
"Shift-W"))
|
||||
idaapi.install_hexrays_callback(i.event_callback)
|
||||
else:
|
||||
print 'Force lvar width: hexrays is not available.'
|
||||
class ForceLvarWidth(idaapi.plugin_t):
|
||||
flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
|
||||
wanted_hotkey = "Shift-W"
|
||||
comment = "Force lvar width plugin for Hex-Rays decompiler"
|
||||
help = "There is no one to help you now"
|
||||
wanted_name = "Hex-Rays lvar width forcer"
|
||||
|
||||
def init(self):
|
||||
if idaapi.init_hexrays_plugin():
|
||||
i = hexrays_callback_info()
|
||||
idaapi.register_action(
|
||||
idaapi.action_desc_t(
|
||||
force_width_actname,
|
||||
"Force lvar width",
|
||||
force_width_action_handler_t(i),
|
||||
"Shift-W"))
|
||||
idaapi.install_hexrays_callback(i.event_callback)
|
||||
print 'Hex-Rays lvar width forcer by ecx86 loaded!'
|
||||
else:
|
||||
print 'Force lvar width: Hexrays is not available.'
|
||||
|
||||
def term(self):
|
||||
pass
|
||||
|
||||
def run(self, arg):
|
||||
pass
|
||||
|
||||
def PLUGIN_ENTRY():
|
||||
return ForceLvarWidth()
|
||||
BIN
plugins/labeless_ida_70.dll
Normal file
BIN
plugins/labeless_ida_70.dll
Normal file
Binary file not shown.
BIN
plugins/labeless_ida_70_64.dll
Normal file
BIN
plugins/labeless_ida_70_64.dll
Normal file
Binary file not shown.
4
plugins/ret-sync.py
Normal file
4
plugins/ret-sync.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from ret_sync_ext_ida import SyncPlugin
|
||||
|
||||
def PLUGIN_ENTRY():
|
||||
return SyncPlugin.SyncPlugin()
|
||||
1124
plugins/ret_sync_ext_ida/SyncPlugin.py
Normal file
1124
plugins/ret_sync_ext_ida/SyncPlugin.py
Normal file
File diff suppressed because it is too large
Load Diff
0
plugins/ret_sync_ext_ida/__init__.py
Normal file
0
plugins/ret_sync_ext_ida/__init__.py
Normal file
296
plugins/ret_sync_ext_ida/broker.py
Normal file
296
plugins/ret_sync_ext_ida/broker.py
Normal file
@@ -0,0 +1,296 @@
|
||||
#
|
||||
# Copyright (C) 2016, Alexandre Gazet.
|
||||
#
|
||||
# Copyright (C) 2012-2015, Quarkslab.
|
||||
#
|
||||
# This file is part of ret-sync.
|
||||
#
|
||||
# ret-sync 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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Note that broker.py is executed by IDA Pro so it is not possible to see
|
||||
# any output using print() or similar
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
import shlex
|
||||
import argparse
|
||||
import subprocess
|
||||
import socket
|
||||
import select
|
||||
import binascii
|
||||
import ConfigParser
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
print "[-] failed to import json\n%s" % repr(sys.exc_info())
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
RUN_DISPATCHER_MAX_ATTEMPT = 4
|
||||
HOST = "localhost"
|
||||
PORT = 9100
|
||||
|
||||
# default value is current script's path
|
||||
DISPATCHER_PATH = os.path.join(os.path.realpath(os.path.dirname(__file__)), "dispatcher.py")
|
||||
if not os.path.exists(DISPATCHER_PATH):
|
||||
print "[-] dispatcher path is not properly set, current value: <%s>" % DISPATCHER_PATH
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
class Client():
|
||||
|
||||
def __init__(self, s):
|
||||
self.sock = s
|
||||
self.buffer = ''
|
||||
|
||||
def feed(self, data):
|
||||
batch = []
|
||||
self.buffer = ''.join([self.buffer, data])
|
||||
if self.buffer.endswith("\n"):
|
||||
batch = [req for req in self.buffer.strip().split('\n') if req != '']
|
||||
self.buffer = ''
|
||||
|
||||
return batch
|
||||
|
||||
|
||||
class BrokerSrv():
|
||||
|
||||
def puts(self, msg):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
|
||||
def announcement(self, msg):
|
||||
self.puts("[sync]{\"type\":\"broker\",\"subtype\":\"msg\",\"msg\":\"%s\"}\n" % msg)
|
||||
|
||||
def notice_idb(self, msg):
|
||||
self.puts("[sync]{\"type\":\"broker\",\"subtype\":\"notice\",\"port\":\"%d\"}\n" % msg)
|
||||
|
||||
def notice_dispatcher(self, type, args=None):
|
||||
if args:
|
||||
notice = "[notice]{\"type\":\"%s\",%s}\n" % (type, args)
|
||||
else:
|
||||
notice = "[notice]{\"type\":\"%s\"}\n" % (type)
|
||||
|
||||
self.notify_socket.sendall(notice)
|
||||
|
||||
def bind(self):
|
||||
self.srv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.srv_sock.bind(('localhost', 0))
|
||||
self.srv_port = self.srv_sock.getsockname()[1]
|
||||
|
||||
def run_dispatcher(self):
|
||||
cmdline = "\"%s\" -u \"%s\"" % (os.path.join(PYTHON_PATH, PYTHON_BIN), DISPATCHER_PATH)
|
||||
tokenizer = shlex.shlex(cmdline)
|
||||
tokenizer.whitespace_split = True
|
||||
args = [arg.replace('\"', '') for arg in list(tokenizer)]
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(args, shell=False, close_fds=True)
|
||||
pid = proc.pid
|
||||
except:
|
||||
pid = None
|
||||
self.announcement("failed to run dispatcher")
|
||||
|
||||
time.sleep(0.2)
|
||||
return pid
|
||||
|
||||
def notify(self):
|
||||
for attempt in range(RUN_DISPATCHER_MAX_ATTEMPT):
|
||||
try:
|
||||
self.notify_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.notify_socket.connect((HOST, PORT))
|
||||
break
|
||||
except:
|
||||
self.notify_socket.close()
|
||||
if (attempt != 0):
|
||||
self.announcement("failed to connect to dispatcher (attempt %d)" % (attempt))
|
||||
if (attempt == (RUN_DISPATCHER_MAX_ATTEMPT - 1)):
|
||||
self.announcement("failed to connect to dispatcher, too much attempts, exiting...")
|
||||
sys.exit()
|
||||
|
||||
self.announcement("dispatcher not found, trying to run it")
|
||||
pid = self.run_dispatcher()
|
||||
if pid:
|
||||
self.announcement("dispatcher now runs with pid: %d" % (pid))
|
||||
|
||||
time.sleep(0.1)
|
||||
self.notice_dispatcher("new_client", "\"port\":%d,\"idb\":\"%s\"" % (self.srv_port, self.name))
|
||||
self.announcement('connected to dispatcher')
|
||||
self.notice_idb(self.srv_port)
|
||||
|
||||
def accept(self):
|
||||
new_socket, addr = self.srv_sock.accept()
|
||||
self.clients_list.append(Client(new_socket))
|
||||
self.opened_sockets.append(new_socket)
|
||||
|
||||
def close(self, s):
|
||||
client = [client for client in self.clients_list if (client.sock == s)]
|
||||
if len(client) == 1:
|
||||
self.clients_list.remove(client[0])
|
||||
s.close()
|
||||
self.opened_sockets.remove(s)
|
||||
|
||||
def recvall(self, client):
|
||||
try:
|
||||
data = client.sock.recv(4096)
|
||||
if data == '':
|
||||
raise
|
||||
except:
|
||||
self.announcement("dispatcher connection error, quitting")
|
||||
sys.exit()
|
||||
|
||||
return client.feed(data)
|
||||
|
||||
def req_dispatcher(self, s, hash):
|
||||
subtype = hash['subtype']
|
||||
if (subtype == 'msg'):
|
||||
msg = hash['msg']
|
||||
self.announcement("dispatcher msg: %s" % msg)
|
||||
|
||||
def req_cmd(self, s, hash):
|
||||
cmd = hash['cmd']
|
||||
self.notice_dispatcher("cmd", "\"cmd\":\"%s\"" % cmd)
|
||||
|
||||
def req_kill(self, s, hash):
|
||||
self.notice_dispatcher("kill")
|
||||
self.announcement("received kill notice")
|
||||
for s in ([self.srv_sock] + self.opened_sockets):
|
||||
s.close()
|
||||
sys.exit()
|
||||
|
||||
def parse_exec(self, s, req):
|
||||
if not (req[0:8] == '[notice]'):
|
||||
self.puts(req)
|
||||
return
|
||||
|
||||
req = self.normalize(req, 8)
|
||||
|
||||
try:
|
||||
hash = json.loads(req)
|
||||
except:
|
||||
print "[-] broker failed to parse json\n %s" % repr(req)
|
||||
return
|
||||
|
||||
type = hash['type']
|
||||
if not type in self.req_handlers:
|
||||
print ("[*] broker unknown request: %s" % type)
|
||||
return
|
||||
|
||||
req_handler = self.req_handlers[type]
|
||||
req_handler(s, hash)
|
||||
|
||||
def normalize(self, req, taglen):
|
||||
req = req[taglen:]
|
||||
req = req.replace("\\", "\\\\")
|
||||
req = req.replace("\n", "")
|
||||
return req
|
||||
|
||||
def handle(self, s):
|
||||
client = [client for client in self.clients_list if (client.sock == s)]
|
||||
if len(client) == 1:
|
||||
batch = self.recvall(client[0])
|
||||
else:
|
||||
self.announcement("socket error")
|
||||
raise Exception("rabbit eating the cable")
|
||||
|
||||
for req in batch:
|
||||
if req != '':
|
||||
self.parse_exec(s, req)
|
||||
|
||||
def loop(self):
|
||||
self.srv_sock.listen(5)
|
||||
while True:
|
||||
rlist, wlist, xlist = select.select([self.srv_sock] + self.opened_sockets, [], [])
|
||||
|
||||
if not rlist:
|
||||
self.announcement("socket error: select")
|
||||
raise Exception("rabbit eating the cable")
|
||||
|
||||
for s in rlist:
|
||||
if s is self.srv_sock:
|
||||
self.accept()
|
||||
else:
|
||||
self.handle(s)
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.opened_sockets = []
|
||||
self.clients_list = []
|
||||
self.pat = re.compile('dbg disconnected')
|
||||
self.req_handlers = {
|
||||
'dispatcher': self.req_dispatcher,
|
||||
'cmd': self.req_cmd,
|
||||
'kill': self.req_kill
|
||||
}
|
||||
|
||||
|
||||
def err_log(msg):
|
||||
fd = open("%s.err" % __file__, 'w')
|
||||
fd.write(msg)
|
||||
fd.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
PYTHON_PATH = os.environ['PYTHON_PATH']
|
||||
PYTHON_BIN = os.environ['PYTHON_BIN']
|
||||
except Exception as e:
|
||||
err_log("broker failed to retreive PYTHON_PATH or PYTHON_BIN value.")
|
||||
sys.exit()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--idb', nargs=1, action='store')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.idb:
|
||||
print "[sync] no idb argument"
|
||||
sys.exit()
|
||||
|
||||
for loc in ['IDB_PATH', 'USERPROFILE', 'HOME']:
|
||||
if loc in os.environ:
|
||||
confpath = os.path.join(os.path.realpath(os.environ[loc]), '.sync')
|
||||
if os.path.exists(confpath):
|
||||
config = ConfigParser.SafeConfigParser({'port': PORT, 'host': HOST})
|
||||
config.read(confpath)
|
||||
PORT = config.getint("INTERFACE", 'port')
|
||||
HOST = config.get("INTERFACE", 'host')
|
||||
break
|
||||
|
||||
server = BrokerSrv(args.idb[0])
|
||||
|
||||
try:
|
||||
server.bind()
|
||||
except Exception as e:
|
||||
server.announcement("failed to bind")
|
||||
err_log(repr(e))
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
server.notify()
|
||||
except Exception as e:
|
||||
server.announcement("failed to notify dispatcher")
|
||||
err_log(repr(e))
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
server.loop()
|
||||
except Exception as e:
|
||||
server.announcement("broker stop")
|
||||
err_log(repr(e))
|
||||
463
plugins/ret_sync_ext_ida/dispatcher.py
Normal file
463
plugins/ret_sync_ext_ida/dispatcher.py
Normal file
@@ -0,0 +1,463 @@
|
||||
#
|
||||
# Copyright (C) 2016, Alexandre Gazet.
|
||||
#
|
||||
# Copyright (C) 2012-2014, Quarkslab.
|
||||
#
|
||||
# This file is part of ret-sync.
|
||||
#
|
||||
# ret-sync 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, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import os.path as altpath
|
||||
import sys
|
||||
import socket
|
||||
import select
|
||||
import base64
|
||||
import binascii
|
||||
import re
|
||||
import ConfigParser
|
||||
import traceback
|
||||
|
||||
HOST = 'localhost'
|
||||
PORT = 9100
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
print "[-] failed to import json\n%s" % repr(sys.exc_info())
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
class Client():
|
||||
|
||||
def __init__(self, s_client, s_srv, name):
|
||||
self.client_sock = s_client
|
||||
self.srv_sock = s_srv
|
||||
self.name = name
|
||||
self.enabled = False
|
||||
self.buffer = ''
|
||||
|
||||
def close(self):
|
||||
self.enabled = False
|
||||
if self.client_sock:
|
||||
self.client_sock.close()
|
||||
if self.srv_sock:
|
||||
self.srv_sock.close()
|
||||
|
||||
def feed(self, data):
|
||||
batch = []
|
||||
self.buffer = ''.join([self.buffer, data])
|
||||
if self.buffer.endswith("\n"):
|
||||
batch = [req for req in self.buffer.strip().split('\n') if req != '']
|
||||
self.buffer = ''
|
||||
|
||||
return batch
|
||||
|
||||
|
||||
class DispatcherSrv():
|
||||
|
||||
def __init__(self):
|
||||
self.idb_clients = []
|
||||
self.dbg_client = None
|
||||
self.srv_socks = []
|
||||
self.opened_socks = []
|
||||
|
||||
self.current_dbg = None
|
||||
self.current_dialect = 'unknown'
|
||||
self.current_idb = None
|
||||
self.current_module = None
|
||||
|
||||
self.sync_mode_auto = True
|
||||
self.disconn_pat = re.compile('dbg disconnected')
|
||||
self.req_handlers = {
|
||||
'new_client': self.req_new_client,
|
||||
'new_dbg': self.req_new_dbg,
|
||||
'dbg_quit': self.req_dbg_quit,
|
||||
'idb_n': self.req_idb_n,
|
||||
'idb_list': self.req_idb_list,
|
||||
'module': self.req_module,
|
||||
'sync_mode': self.req_sync_mode,
|
||||
'cmd': self.req_cmd,
|
||||
'bc': self.req_bc,
|
||||
'kill': self.req_kill
|
||||
}
|
||||
|
||||
def bind(self, host, port):
|
||||
self.dbg_srv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.dbg_srv_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.dbg_srv_sock.bind((host, port))
|
||||
self.srv_socks.append(self.dbg_srv_sock)
|
||||
|
||||
if not (socket.gethostbyname(host) == '127.0.0.1'):
|
||||
self.localhost_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.localhost_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.localhost_sock.bind(('localhost', port))
|
||||
self.srv_socks.append(self.localhost_sock)
|
||||
|
||||
def accept(self, s):
|
||||
new_socket, addr = s.accept()
|
||||
self.opened_socks.append(new_socket)
|
||||
|
||||
def listen(self):
|
||||
for s in self.srv_socks:
|
||||
s.listen(5)
|
||||
|
||||
def close(self, s):
|
||||
s.close()
|
||||
self.opened_socks.remove(s)
|
||||
|
||||
def loop(self):
|
||||
self.listen()
|
||||
self.announcement("dispatcher listening")
|
||||
|
||||
while True:
|
||||
rlist, wlist, xlist = select.select(self.srv_socks + self.opened_socks, [], [])
|
||||
|
||||
if not rlist:
|
||||
self.announcement("socket error: select")
|
||||
raise Exception("rabbit eating the cable")
|
||||
|
||||
for s in rlist:
|
||||
if s in self.srv_socks:
|
||||
self.accept(s)
|
||||
else:
|
||||
self.handle(s)
|
||||
|
||||
def handle(self, s):
|
||||
client = self.sock_to_client(s)
|
||||
for req in self.recvall(client):
|
||||
self.parse_exec(s, req)
|
||||
|
||||
# find client object for its srv socket
|
||||
def sock_to_client(self, s):
|
||||
if self.current_dbg and (s == self.current_dbg.srv_sock):
|
||||
client = self.current_dbg
|
||||
else:
|
||||
clist = [client for client in self.idb_clients if (client.srv_sock == s)]
|
||||
if not clist:
|
||||
client = Client(None, s, None)
|
||||
self.idb_clients.append(client)
|
||||
else:
|
||||
client = clist[0]
|
||||
|
||||
return client
|
||||
|
||||
# buffered readline like function
|
||||
def recvall(self, client):
|
||||
try:
|
||||
data = client.srv_sock.recv(4096)
|
||||
if data == '':
|
||||
raise
|
||||
except:
|
||||
if client == self.current_dbg:
|
||||
self.broadcast("debugger closed the connection")
|
||||
self.dbg_quit()
|
||||
else:
|
||||
self.client_quit(client.srv_sock)
|
||||
self.broadcast("a client quit, nb client(s) left: %d" % len(self.idb_clients))
|
||||
|
||||
return []
|
||||
|
||||
return client.feed(data)
|
||||
|
||||
# parse and execute requests from clients (idbs or dbg)
|
||||
def parse_exec(self, s, req):
|
||||
if not (req[0:8] == '[notice]'):
|
||||
# this is a normal [sync] request from debugger, forward it
|
||||
self.forward(req)
|
||||
# receive 'dbg disconnected', socket can be closed
|
||||
if re.search(self.disconn_pat, req):
|
||||
self.close(s)
|
||||
return
|
||||
|
||||
req = self.normalize(req, 8)
|
||||
try:
|
||||
hash = json.loads(req)
|
||||
except:
|
||||
print "[-] dispatcher failed to parse json\n %s\n" % req
|
||||
return
|
||||
|
||||
type = hash['type']
|
||||
if not type in self.req_handlers:
|
||||
print ("[*] dispatcher unknown request: %s" % type)
|
||||
return
|
||||
|
||||
req_handler = self.req_handlers[type]
|
||||
req_handler(s, hash)
|
||||
|
||||
def normalize(self, req, taglen):
|
||||
req = req[taglen:]
|
||||
req = req.replace("\\", "\\\\")
|
||||
req = req.replace("\n", "")
|
||||
return req
|
||||
|
||||
def puts(self, msg, s):
|
||||
s.sendall(msg)
|
||||
|
||||
# dispatcher announcements are forwarded to the idb
|
||||
def announcement(self, msg, s=None):
|
||||
if not s:
|
||||
if not self.current_idb:
|
||||
return
|
||||
s = self.current_idb.client_sock
|
||||
|
||||
try:
|
||||
s.sendall("[notice]{\"type\":\"dispatcher\",\"subtype\":\"msg\",\"msg\":\"%s\"}\n" % msg)
|
||||
except:
|
||||
return
|
||||
|
||||
# send message to all connected idb clients
|
||||
def broadcast(self, msg):
|
||||
for idbc in self.idb_clients:
|
||||
self.announcement(msg, idbc.client_sock)
|
||||
|
||||
# send dbg message to currently active idb client
|
||||
def forward(self, msg, s=None):
|
||||
if not s:
|
||||
if not self.current_idb:
|
||||
return
|
||||
s = self.current_idb.client_sock
|
||||
|
||||
if s:
|
||||
s.sendall(msg + "\n")
|
||||
|
||||
# send dbg message to all idb clients
|
||||
def forward_all(self, msg, s=None):
|
||||
for idbc in self.idb_clients:
|
||||
self.forward(msg, idbc.client_sock)
|
||||
|
||||
# disable current idb and enable new idb matched from current module name
|
||||
def switch_idb(self, new_idb):
|
||||
msg = "[sync]{\"type\":\"broker\",\"subtype\":\"%s\"}\n"
|
||||
if (not self.current_idb == new_idb) & (self.current_idb.enabled):
|
||||
self.current_idb.client_sock.sendall(msg % "disable_idb")
|
||||
self.current_idb.enabled = False
|
||||
|
||||
if new_idb:
|
||||
new_idb.client_sock.sendall(msg % "enable_idb")
|
||||
self.current_idb = new_idb
|
||||
new_idb.enabled = True
|
||||
|
||||
# a new idb client connects to the dispatcher via its broker
|
||||
def req_new_client(self, srv_sock, hash):
|
||||
port, name = hash['port'], hash['idb']
|
||||
try:
|
||||
client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
client_sock.connect(('localhost', port))
|
||||
self.opened_socks.append(client_sock)
|
||||
except:
|
||||
self.opened_socks.remove(srv_sock)
|
||||
srv_sock.close()
|
||||
return
|
||||
|
||||
# check if an idb client is already registered with the same name
|
||||
conflicting = [client for client in self.idb_clients if (client.name == name)]
|
||||
|
||||
# promote to idb client
|
||||
new_client = self.sock_to_client(srv_sock)
|
||||
new_client.client_sock = client_sock
|
||||
new_client.name = name
|
||||
self.broadcast("add new client (listening on port %d), nb client(s): %d" % (port, len(self.idb_clients)))
|
||||
|
||||
if conflicting:
|
||||
self.broadcast("conflicting name: %s !" % new_client.name)
|
||||
|
||||
if not self.current_idb:
|
||||
self.current_idb = new_client
|
||||
|
||||
# if new client match current module name, then enable it
|
||||
if self.current_module == name:
|
||||
self.switch_idb(new_client)
|
||||
|
||||
# inform new client about debugger's dialect
|
||||
self.dbg_dialect(new_client)
|
||||
|
||||
# clean state when a client is quiting
|
||||
def client_quit(self, s):
|
||||
self.opened_socks.remove(s)
|
||||
# remove exiting client from the list of active clients
|
||||
for idbc in [idbc for idbc in self.idb_clients if (idbc.srv_sock == s)]:
|
||||
self.idb_clients.remove(idbc)
|
||||
self.opened_socks.remove(idbc.client_sock)
|
||||
idbc.close()
|
||||
|
||||
# no more clients, let's kill ourself
|
||||
if not self.idb_clients:
|
||||
for s in self.srv_socks:
|
||||
s.close()
|
||||
sys.exit()
|
||||
|
||||
# determine if debugger is Windows specific
|
||||
def is_windows_dbg(self, dialect):
|
||||
return (dialect in ['windbg', 'x64_dbg', 'ollydbg2'])
|
||||
|
||||
# a new debugger client connects to the dispatcher
|
||||
def req_new_dbg(self, s, hash):
|
||||
msg = hash['msg']
|
||||
if self.current_dbg:
|
||||
self.dbg_quit()
|
||||
|
||||
# promote to dbg client
|
||||
self.current_dbg = self.sock_to_client(s)
|
||||
self.current_dbg.client_sock = s
|
||||
self.idb_clients.remove(self.current_dbg)
|
||||
|
||||
self.broadcast("new debugger client: %s" % msg)
|
||||
|
||||
# store dbb's dialect
|
||||
if 'dialect' in hash:
|
||||
self.current_dialect = hash['dialect']
|
||||
|
||||
# case when IDA is on a linux/bsd host and connected to remote windows
|
||||
# use ntpath instead of posixpath
|
||||
if sys.platform.startswith('linux') or sys.platform == 'darwin':
|
||||
if self.is_windows_dbg(self.current_dialect):
|
||||
global altpath
|
||||
import ntpath as altpath
|
||||
|
||||
self.dbg_dialect()
|
||||
|
||||
# inform client about debugger's dialect
|
||||
def dbg_dialect(self, client=None):
|
||||
msg = "[sync]{\"type\":\"dialect\",\"dialect\":\"%s\"}\n" % self.current_dialect
|
||||
if client:
|
||||
client.client_sock.sendall(msg)
|
||||
else:
|
||||
for idbc in self.idb_clients:
|
||||
idbc.client_sock.sendall(msg)
|
||||
|
||||
# debugger client disconnect from the dispatcher
|
||||
def req_dbg_quit(self, s, hash):
|
||||
msg = hash['msg']
|
||||
self.broadcast("debugger quit: %s" % msg)
|
||||
self.dbg_quit()
|
||||
|
||||
# clean state when debugger is quiting
|
||||
def dbg_quit(self):
|
||||
self.opened_socks.remove(self.current_dbg.srv_sock)
|
||||
self.current_dbg.close()
|
||||
self.current_dbg = None
|
||||
self.current_module = None
|
||||
self.switch_idb(None)
|
||||
self.current_dialect = 'unknown'
|
||||
|
||||
# handle kill notice from a client, exit properly if no more client
|
||||
def req_kill(self, s, hash):
|
||||
self.client_quit(s)
|
||||
self.broadcast("received a kill notice from client, %d client(s) left" % len(self.idb_clients))
|
||||
|
||||
# send list of currently connected idb clients
|
||||
def req_idb_list(self, s, hash):
|
||||
clist = "> currently connected idb(s):\n"
|
||||
if not self.idb_clients:
|
||||
clist += " no idb client yet\n"
|
||||
else:
|
||||
for i in range(len(self.idb_clients)):
|
||||
clist += (" [%d] %s\n" % (i, self.idb_clients[i].name))
|
||||
|
||||
s.sendall(clist)
|
||||
|
||||
# manually set current active idb to idb n from idb list
|
||||
def req_idb_n(self, s, hash):
|
||||
idb = hash['idb']
|
||||
try:
|
||||
idbn = int(idb)
|
||||
except:
|
||||
s.sendall("> n should be a decimal value")
|
||||
return
|
||||
|
||||
try:
|
||||
idbc = self.idb_clients[idbn]
|
||||
except:
|
||||
s.sendall("> %d is invalid (see idblist)" % idbn)
|
||||
return
|
||||
|
||||
self.switch_idb(idbc)
|
||||
s.sendall("> current idb set to %d" % idbn)
|
||||
|
||||
# dbg notice that its current module has changed
|
||||
def req_module(self, s, hash):
|
||||
modpath = hash['path']
|
||||
self.current_module = modname = altpath.basename(modpath)
|
||||
matching = [idbc for idbc in self.idb_clients if (idbc.name.lower() == modname.lower())]
|
||||
|
||||
if not self.sync_mode_auto:
|
||||
self.broadcast("sync_mode_auto off")
|
||||
return
|
||||
|
||||
if len(matching) == 1:
|
||||
# matched is set as active
|
||||
self.switch_idb(matching[0])
|
||||
else:
|
||||
if not len(matching):
|
||||
msg = "mod request has no match for %s"
|
||||
else:
|
||||
msg = "ambiguous mod request, too many matches for %s"
|
||||
|
||||
self.broadcast(msg % modname)
|
||||
# no match current idb (if existing) is disabled
|
||||
if self.current_idb.enabled:
|
||||
self.switch_idb(None)
|
||||
|
||||
# sync mode tells if idb switch is automatic or manual
|
||||
def req_sync_mode(self, s, hash):
|
||||
mode = hash['auto']
|
||||
self.broadcast("sync mode auto set to %s" % mode)
|
||||
self.sync_mode_auto = (mode == "on")
|
||||
|
||||
# bc request should be forwarded to all idbs
|
||||
def req_bc(self, s, hash):
|
||||
msg = "[sync]%s" % json.dumps(hash)
|
||||
self.forward_all(msg)
|
||||
|
||||
def req_cmd(self, s, hash):
|
||||
cmd = hash['cmd']
|
||||
self.current_dbg.client_sock.sendall("%s\n" % cmd)
|
||||
|
||||
|
||||
def err_log(msg):
|
||||
fd = open("%s.err" % __file__, 'w')
|
||||
fd.write(msg)
|
||||
fd.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
server = DispatcherSrv()
|
||||
|
||||
for loc in ['IDB_PATH', 'USERPROFILE', 'HOME']:
|
||||
if loc in os.environ:
|
||||
confpath = os.path.join(os.path.realpath(os.environ[loc]), '.sync')
|
||||
if os.path.exists(confpath):
|
||||
config = ConfigParser.SafeConfigParser({'host': HOST, 'port': PORT})
|
||||
config.read(confpath)
|
||||
HOST = config.get("INTERFACE", 'host')
|
||||
PORT = config.getint("INTERFACE", 'port')
|
||||
server.announcement("configuration file loaded")
|
||||
break
|
||||
|
||||
try:
|
||||
server.bind(HOST, PORT)
|
||||
except Exception as e:
|
||||
err_log("dispatcher failed to bind on %s:%s\n-> %s" % (HOST, PORT, repr(e)))
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
server.loop()
|
||||
except Exception as e:
|
||||
err_log("dispatcher failed\n-> %s" % repr(e))
|
||||
server.announcement("dispatcher stop")
|
||||
5
skin/ida-default/manifest.xml
Normal file
5
skin/ida-default/manifest.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<theme name="IDA default"
|
||||
author="Hex-Rays"
|
||||
version="v1.0">
|
||||
</theme>
|
||||
20
skin/ida-default/stylesheet.qss
Normal file
20
skin/ida-default/stylesheet.qss
Normal file
@@ -0,0 +1,20 @@
|
||||
CustomIDAMemo {
|
||||
font-family: "<TEXT_INPUT_FONT_FAMILY>";
|
||||
font-size: <TEXT_INPUT_FONT_SIZE>;
|
||||
font-style: <TEXT_INPUT_FONT_STYLE>;
|
||||
font-weight: <TEXT_INPUT_FONT_WEIGHT>;
|
||||
}
|
||||
|
||||
IDAView {
|
||||
font-family: "<DISASSEMBLY_FONT_FAMILY>";
|
||||
font-size: <DISASSEMBLY_FONT_SIZE>;
|
||||
font-style: <DISASSEMBLY_FONT_STYLE>;
|
||||
font-weight: <DISASSEMBLY_FONT_WEIGHT>;
|
||||
}
|
||||
|
||||
hexview_t {
|
||||
font-family: "<HEXVIEW_FONT_FAMILY>";
|
||||
font-size: <HEXVIEW_FONT_SIZE>;
|
||||
font-style: <HEXVIEW_FONT_STYLE>;
|
||||
font-weight: <HEXVIEW_FONT_WEIGHT>;
|
||||
}
|
||||
BIN
skin/idaskins-dark/icons/expand.png
Normal file
BIN
skin/idaskins-dark/icons/expand.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
skin/idaskins-dark/icons/spacer.png
Normal file
BIN
skin/idaskins-dark/icons/spacer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
7
skin/idaskins-dark/manifest.xml
Normal file
7
skin/idaskins-dark/manifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<theme name="IDASkins dark"
|
||||
author="athre0z"
|
||||
version="v1.0"
|
||||
preview_image="preview.png"
|
||||
notes="Combine with idaConsonance text-color theme for maximum epicness!">
|
||||
</theme>
|
||||
BIN
skin/idaskins-dark/preview.png
Normal file
BIN
skin/idaskins-dark/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
225
skin/idaskins-dark/stylesheet.qss
Normal file
225
skin/idaskins-dark/stylesheet.qss
Normal file
@@ -0,0 +1,225 @@
|
||||
QWidget {
|
||||
background-color: #363636;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
QCheckBox {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
QTextEdit {
|
||||
background-color: #2d2d2d;
|
||||
border: 1px solid #363636;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
QMenuBar, QMenuBar::item {
|
||||
background-color: #444444;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
QMenu::item:selected {
|
||||
background-color: #2A2A2A;
|
||||
}
|
||||
|
||||
QLineEdit {
|
||||
border: 1px solid #474747;
|
||||
min-height: 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
QLineEdit:hover, QLineEdit:focus {
|
||||
border: 1px solid #00aaaa;
|
||||
}
|
||||
|
||||
QTabBar::tab {
|
||||
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #555555, stop: 1 #444444);
|
||||
}
|
||||
|
||||
QTabBar::tab:selected {
|
||||
background-color: #777777;
|
||||
}
|
||||
|
||||
QTableView {
|
||||
border: 1px solid #474747;
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
QHeaderView::section {
|
||||
background-color: #444;
|
||||
border: none;
|
||||
border-left: 1px solid #666;
|
||||
border-right: 1px solid #333;
|
||||
padding-top: 3px;
|
||||
padding-left: 4px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
QTableCornerButton::section {
|
||||
background: #222;
|
||||
border: 2px outset #222;
|
||||
}
|
||||
|
||||
IDAView, hexview_t, CustomIDAMemo {
|
||||
border: none;
|
||||
}
|
||||
|
||||
CustomIDAMemo, EditContainer {
|
||||
font-family: "<TEXT_INPUT_FONT_FAMILY>";
|
||||
font-size: <TEXT_INPUT_FONT_SIZE>;
|
||||
font-style: <TEXT_INPUT_FONT_STYLE>;
|
||||
font-weight: <TEXT_INPUT_FONT_WEIGHT>;
|
||||
}
|
||||
|
||||
IDAView {
|
||||
font-family: "<DISASSEMBLY_FONT_FAMILY>";
|
||||
font-size: <DISASSEMBLY_FONT_SIZE>;
|
||||
font-style: <DISASSEMBLY_FONT_STYLE>;
|
||||
font-weight: <DISASSEMBLY_FONT_WEIGHT>;
|
||||
}
|
||||
|
||||
hexview_t {
|
||||
font-family: "<HEXVIEW_FONT_FAMILY>";
|
||||
font-size: <HEXVIEW_FONT_SIZE>;
|
||||
font-style: <HEXVIEW_FONT_STYLE>;
|
||||
font-weight: <HEXVIEW_FONT_WEIGHT>;
|
||||
}
|
||||
|
||||
/* TODO: DEBUG_REGISTERS, OUTPUT_WINDOW */
|
||||
|
||||
QScrollBar {
|
||||
background-color: #363636;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0 0 0 0;
|
||||
}
|
||||
|
||||
QScrollBar::sub-line, QScrollBar::add-line {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
QScrollBar::add-page, QScrollBar::sub-page {
|
||||
background: none;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal {
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
QScrollBar::handle {
|
||||
background-color: #585858;
|
||||
margin: 3px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
QToolBar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
QPushButton {
|
||||
border: 1px solid #077;
|
||||
text-align: center;
|
||||
min-height: 20px;
|
||||
min-width: 50px;
|
||||
padding: 0 6px 0 6px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
QPushButton:hover, QPushButton:default {
|
||||
border: 1px solid #0aa;
|
||||
}
|
||||
|
||||
QPushButton:pressed {
|
||||
border: 1px solid #0ee;
|
||||
}
|
||||
|
||||
QComboBox {
|
||||
border: 1px solid #474747;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
QComboBox > QLineEdit, QComboBox > QLineEdit:hover, QComboBox > QLineEdit:focus {
|
||||
border: none;
|
||||
min-height: default;
|
||||
}
|
||||
|
||||
QComboBox:hover, QComboBox:focus {
|
||||
border: 1px solid #00aaaa;
|
||||
}
|
||||
|
||||
QComboBox::drop-down {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: top right;
|
||||
width: 15px;
|
||||
|
||||
border-left-width: 1px;
|
||||
border-left-color: #666;
|
||||
border-left-style: solid;
|
||||
}
|
||||
|
||||
QComboBox::down-arrow {
|
||||
image: url(<SKINDIR>/icons/expand.png);
|
||||
}
|
||||
|
||||
/* Close, maximize and undock button for dock widgets */
|
||||
IDADockWidget > QWidget > QAbstractButton {
|
||||
background-color: #666;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QRadioButton, QLabel, QCheckBox {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
TNavBand > QPushButton, RegJumpButton {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: 0 0 0 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
EditContainer, ChooserContainer, QGroupBox, QListView, QTreeView {
|
||||
border: 1px solid #606060;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
QGroupBox {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top center;
|
||||
}
|
||||
|
||||
/* Remove border from IDC/Python switch button */
|
||||
CLIWidget > QGroupBox > QPushButton,
|
||||
CLIWidget > QGroupBox > QPushButton:hover,
|
||||
CLIWidget > QGroupBox > QPushButton:focus {
|
||||
border: none;
|
||||
}
|
||||
|
||||
CLIWidget > QGroupBox {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
QTreeView::item:selected, QListView::item:selected, QTableView::item:selected {
|
||||
background-color: #474747;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
QToolTip, QTipLabel {
|
||||
border: 1px solid #AA5500;
|
||||
border-radius: 3px;
|
||||
background: #111111;
|
||||
color: #ddd;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user