Making a Custom IOCTL Calls Tree

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.

Access the Python API documentation through Help->Python API Documentation. The plugin pages contain sample scripts.

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. 

Update the name or style in graph_template.upy to avoid clashing with the template plugin.

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”.

Run the interactive report “API Info” from the right click menu of the entity.

At the bottom of the report is the uniquename. I can copy it from the right click menu:

Get the unique name from the report. The unique name is an identifier that allows you to find that entity again.

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:

@[email protected][email protected]/driver.c,@[email protected]@l./driver.c
@[email protected][email protected]/driver.c,@[email protected]@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:

Update the entity refs call with a custom function that can include additional 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:

Import os.path to allow checking for the existence of the oracle file
Actually load the oracle file in the draw function

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.

Drag the .upy file onto Understand to install it.

Now, when I create a “Calls” graph for main, I’ll see the new style name in the variant drop down list.

The initial Calls graph for main. The variant drop down includes the new plugin.

My new graph looks like this:

The result of the graph plugin

The edge from main to WR_VALUE is synced to the use reference:

Edges from functions to the IOCTL commands show sync 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.

Edges from IOCTL entities connect to the driver function use.