Last active
March 3, 2023 12:00
-
-
Save Informatic/e28acada58e502540315b51b84a6f996 to your computer and use it in GitHub Desktop.
Ghidra script to extract wayland protocols definition from wayland-scanner generated client library. Run this on an (opened and analysed) libwebos-*-client.so library.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This Ghidra script will attempt to extract wayland protocol/interfaces | |
# definitions xml from wayland-scanner-generated built client library. | |
# | |
# This should properly handler interfaces, methods and events, and their arg | |
# types and order. Argument names or descriptions cannot be extracted. | |
# | |
# @author infowski | |
# @category _NEW_ | |
# @keybinding | |
# @menupath | |
# @toolbar | |
# import sys | |
from ghidra.program.model.data import DataTypeConflictHandler, PointerDataType | |
from ghidra.app.util.cparser.C import CParser | |
# see: | |
# https://manpages.debian.org/experimental/libwayland-doc/wl_interface.3.en.html | |
# https://manpages.debian.org/experimental/libwayland-doc/wl_message.3.en.html | |
def read_string(addr, mem=currentProgram.getMemory()): | |
buf = bytearray() | |
while True: | |
c = mem.getByte(addr) | |
if c == 0: | |
return buf.decode() | |
buf.append(c) | |
addr = addr.add(1) | |
wl_message_struct = """ | |
typedef struct { | |
char* name; | |
char* signature; | |
struct wl_interface** types; | |
} wl_message;""" | |
wl_interface_struct = """ | |
typedef struct { | |
char* name; | |
int version; | |
int method_count; | |
struct wl_message* methods; | |
int event_count; | |
struct wl_message* events; | |
} wl_interface;""" | |
# Get Data Type Manager | |
data_type_manager = currentProgram.getDataTypeManager() | |
# Create CParser | |
parser = CParser(data_type_manager) | |
print("Adding wl_message") | |
wl_message = parser.parse(wl_message_struct) | |
data_type_manager.addDataType(wl_message, DataTypeConflictHandler.REPLACE_HANDLER) | |
print("Adding wl_interface") | |
wl_interface = parser.parse(wl_interface_struct) | |
data_type_manager.addDataType(wl_interface, DataTypeConflictHandler.REPLACE_HANDLER) | |
types_def = { | |
"i": "int", | |
"u": "uint", | |
"f": "fixed", | |
"s": "string", | |
"o": "object", | |
"n": "new_id", | |
"a": "array", | |
"h": "fd", | |
} | |
def parse_messages(base_addr, cnt): | |
result = [] | |
for n in range(cnt): | |
addr = base_addr.add(wl_message.getLength() * n) | |
clearListing(addr, addr.add(wl_message.getLength() - 1)) | |
meth = createData(addr, wl_message) | |
m_name = read_string(meth.getComponent(0).getValue()) | |
m_signature = read_string(meth.getComponent(1).getValue()) | |
type_offset = 0 | |
args = [] | |
nullable = False | |
since = 0 | |
for arg in m_signature: | |
if arg == "?": | |
nullable = True | |
elif arg in "no": | |
clearListing(meth.getComponent(2).getValue().add(4 * type_offset)) | |
createData( | |
meth.getComponent(2).getValue().add(4 * type_offset), | |
PointerDataType(wl_interface), | |
) | |
interface = getSymbolAt( | |
getDataAt( | |
meth.getComponent(2).getValue().add(4 * type_offset) | |
).getValue() | |
).getName() | |
if interface.endswith("_interface"): | |
interface = interface[: -len("_interface")] | |
type_offset += 1 | |
args.append( | |
{ | |
"type": types_def.get(arg), | |
"interface": interface, | |
"nullable": nullable, | |
} | |
) | |
nullable = False | |
elif arg in types_def: | |
args.append( | |
{ | |
"type": types_def.get(arg), | |
"nullable": nullable, | |
} | |
) | |
nullable = False | |
type_offset += 1 | |
else: | |
since = int(arg) | |
result.append({"name": m_name, "args": args, "since": since}) | |
return result | |
def render_messages(messages, t): | |
for idx, msg in enumerate(messages): | |
print(" * [%2d] %-6s %s" % (idx, t, msg["name"])) | |
for arg in msg["args"]: | |
print( | |
" -> Argument %s %s" | |
% (arg.get("type"), arg.get("interface") or "") | |
) | |
def render_messages_xml(messages, t, fd): | |
for msg in messages: | |
fd.write('\t\t<%s name="%s">\n' % (t, msg["name"])) | |
for idx, arg in enumerate(msg["args"]): | |
fd.write( | |
'\t\t\t<arg name="arg%d" type="%s" %s/>\n' | |
% ( | |
idx, | |
arg["type"], | |
'interface="%s" ' % (arg["interface"],) | |
if "interface" in arg | |
else "", | |
) | |
) | |
fd.write("\t\t</%s>\n" % (t,)) | |
interfaces = {} | |
for sym in currentProgram.getSymbolTable().getDefinedSymbols(): | |
if sym.getName().endswith("_interface") and sym.isExternalEntryPoint(): | |
try: | |
clearListing( | |
sym.getAddress(), sym.getAddress().add(wl_interface.getLength() - 1) | |
) | |
data = createData(sym.getAddress(), wl_interface) | |
if not data.getComponent(0).getValue(): | |
continue | |
name = read_string(data.getComponent(0).getValue()) | |
version = int(data.getComponent(1).getValue().getValue()) | |
methods = ( | |
parse_messages( | |
data.getComponent(3).getValue(), | |
data.getComponent(2).getValue().getValue(), | |
) | |
if data.getComponent(2).getValue().getValue() | |
else [] | |
) | |
events = ( | |
parse_messages( | |
data.getComponent(5).getValue(), | |
data.getComponent(4).getValue().getValue(), | |
) | |
if data.getComponent(4).getValue().getValue() | |
else [] | |
) | |
interfaces[name] = { | |
"name": name, | |
"version": version, | |
"methods": methods, | |
"events": events, | |
} | |
except: | |
raise | |
# print(sys.exc_info()) | |
break | |
target_file = askFile("FILE", "Choose file:") | |
print("file:", target_file) | |
with open(str(target_file), "w+") as fd: | |
fd.write( | |
"""<?xml version="1.0" encoding="UTF-8"?> | |
<protocol name="wayland"> | |
""" | |
) | |
for ifname in sorted(interfaces.keys()): | |
iface = interfaces[ifname] | |
# print("Interface %s (version: %d)" % (iface["name"], iface["version"])) | |
# render_messages(iface["methods"], "Method") | |
# render_messages(iface["events"], "Event") | |
fd.write( | |
'\t<interface name="%s" version="%s">\n' % (iface["name"], iface["version"]) | |
) | |
render_messages_xml(iface["methods"], "request", fd) | |
render_messages_xml(iface["events"], "event", fd) | |
fd.write("\t</interface>\n") | |
fd.write("</protocol>\n") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment