Abstract: A sample plugin to add edges between IOCTL commands and driver code.
Recently there was a request for a calls graph that could show calls through an Input and Output Control (IOCTL) interface. It’s possible that someday Understand will natively support creating references from an IOCTL call to the driver function but, in the meantime, we can use a graph plugin to show the custom edges.
I’ll start from the python API graph template that’s part of the documentation. An earlier version of that script is described in this article. You can access it through the documentation or download it below.
First, it’s always a good idea to update either the style or the name so it doesn’t clash with other plugins. I’ll update the style.
Defining an IOCTL Oracle File
Understand doesn’t have the connection between the IOCTL command and the function defining the IOCTL command. So, we need to get that knowledge from somewhere else. I’m going to follow the precedent in this article and load an external csv file. My csv file name will be based on the database name so I can use this template for any database. It will contain two columns; the first will have the unique name of the IOCTL command, the second will contain the unique name of the function that the kernel will call with that command.
For a sample, I’ll use this tutorial code. To find entity unique names, I’ll use the interactive report “API Info”.
At the bottom of the report is the uniquename. I can copy it from the right click menu:
I’ll do the same for RD_VALUE and for the function etx_ioctl defined in driver.c. I want any use of RD_VALUE or WR_VALUE to act like a call to ext_ioctl. So, my csv file will be:
@lWR_VALUE@kc5WR_VALUE=5d9cc39feb7f857a@f./driver.c,@letx_ioctl@p@l./driver.c @lRD_VALUE@kc5RD_VALUE=0bebff62a5c3bc74@f./driver.c,@letx_ioctl@p@l./driver.c
To load the csv file I can basically copy my oracle loading code from the previous article and replace db.lookup_arch() with db.lookup_uniquename(). I won’t have multiple edges, so I’ll make it a map from entity to entity instead of from an entity to a list of entities.
def loadOracle(db):
# Load an oracle file
oracle = dict()
path = db.name()[:-4] + "_ioctl_oracle.csv"
if os.path.exists(path):
fin = open(path,'r')
line = fin.readline()
while line:
parts = line.split(',')
src = db.lookup_uniquename(parts[0].strip())
dest = db.lookup_uniquename(parts[-1].strip())
if src and dest:
oracle[src]=dest
line = fin.readline()
fin.close()
return oracle
Adding the Custom Edges
Now I need to update the references that become edges. I’ll replace the ent.refs() call with a call to my own function so I can include the IOCTL references:
My new refs function will be:
def refsEdges(ent, oracle):
refs = []
if ent in oracle:
# Connect IOCTL entities to the function defining the action
toFunc = oracle.get(ent)
for ref in ent.refs("useby",unique=True):
if ref.ent() == toFunc:
refs.append(ref)
else:
# Build calls like usual
refs = ent.refs("call", unique=True)
if oracle:
for ref in ent.refs("use",unique=True):
# If there's a use reference to an IOCTL entity and this function
# is not the function defining the action, add an edge to the IOCTL
# entity
toFunc = oracle.get(ref.ent())
if toFunc and toFunc != ent:
refs.append(ref)
return refs
Two other changes to make the script functional; I need to add an import for os.path and load the oracle in my draw code:
Running the Plugin
Now I’m ready to test my script. Personally, I save the script in the correct location for Understand to find it. That location is platform specific and described in this article. You can also copy the script to that location by dragging it onto the Understand editor and selecting “Install” on the popup that appears.
Now, when I create a “Calls” graph for main, I’ll see the new style name in the variant drop down list.
My new graph looks like this:
The edge from main to WR_VALUE is synced to the use reference:
The edge from WR_VALUE to etx_ioctl takes me to the point in that code where WR_VALUE is mapped.