Skip to content

Instantly share code, notes, and snippets.

@Informatic
Last active March 3, 2023 12:00
Show Gist options
  • Save Informatic/e28acada58e502540315b51b84a6f996 to your computer and use it in GitHub Desktop.
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 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