User Guide

Commands

Elfcall provides the following sub-commands via the elfcall command line client.

Gen

Gen is used to generate a symbol graph. This is arguably not going to be the best visualization for really large trees, however in this case you likely want to use the client to generate the same graph (we will show both). First, let’s combine some example code from the repository (shown how to clone in Installation.

cd data/
make
cd ../

This will generate data/libfoo.so, a small library we can use for demonstration.

console

The default output prints to the console. Let’s run gen and see what happens!

$ elfcall gen data/libfoo.so

The output is basic in that you will see the linked library followed by undefined symbols we needed. It’s implied that these are needed symbols of libfoo.so.

$ elfcall gen data/libfoo.so
==/usr/lib/x86_64-linux-gnu/libstdc++.so.6==
_ZNSt8ios_base4InitC1Ev    _ZNSt8ios_base4InitD1Ev
==/lib/i386-linux-gnu/libc.so.6==
__cxa_atexit    __cxa_finalize

The above shows where the undefined symbols in our binary of interest are found. Note that this isn’t a graph, hence why we don’t see any kind of entry for the main binary. You can add --debug to see what is searched and when symbols are found:

$ elfcall --debug gen data/libfoo.so
Looking for libstdc++.so.6
Found _ZNSt8ios_base4InitC1Ev -> libstdc++.so.6
Found _ZNSt8ios_base4InitD1Ev -> libstdc++.so.6
Looking for libm.so.6
Looking for libgcc_s.so.1
Looking for libc.so.6
Found __cxa_finalize -> libc.so.6
Found __cxa_atexit -> libc.so.6
Looking for ld-linux-x86-64.so.2
==/usr/lib/x86_64-linux-gnu/libstdc++.so.6==
_ZNSt8ios_base4InitC1Ev    _ZNSt8ios_base4InitD1Ev
==/lib/x86_64-linux-gnu/libc.so.6==
__cxa_atexit    __cxa_finalize

This is actually the default output “format” for gen - printing to the console. There are dIfferent formats for graphs are shown below. To do this same generation in Python:

from elfcall.main import BinaryInterface
cli = BinaryInterface("data/libfoo.so")

# These are the same
cli.gen()
cli.gen(fmt="console")

# Instead of printing, get json output
results = cli.gen_output()

The output “results” will have sets of imported and exported symbols, and then a lookup of what is found and where. Note that this output is consistent between all graph types, hence why the output format is not needed.

text

Text output is akin to console, but we are explicitly printing basic entities for a graph. We are generating the data as if we are writing nodes and relationships in a graph, and (from Python) there is even an optional boolean to indicate we want to see ELF and SYMBOL entries on their own (default is False). This means we will see what the binary of interest is linked to, and a logical relationship for symbols and libs - one library will export a symbol, and another will need it.

$ elfcall gen data/libfoo.so --fmt text
/home/vanessa/Desktop/Code/elfcall/data/libfoo.so  LINKSWITH            /usr/lib/x86_64-linux-gnu/libstdc++.so.6
/home/vanessa/Desktop/Code/elfcall/data/libfoo.so  LINKSWITH            /lib/x86_64-linux-gnu/libc.so.6
/usr/lib/x86_64-linux-gnu/libstdc++.so.6           LINKSWITH            libm.so.6
/usr/lib/x86_64-linux-gnu/libstdc++.so.6           LINKSWITH            /lib/x86_64-linux-gnu/libc.so.6
/usr/lib/x86_64-linux-gnu/libstdc++.so.6           LINKSWITH            ld-linux-x86-64.so.2
/usr/lib/x86_64-linux-gnu/libstdc++.so.6           LINKSWITH            libgcc_s.so.1
/lib/x86_64-linux-gnu/libc.so.6                    LINKSWITH            ld-linux-x86-64.so.2
/usr/lib/x86_64-linux-gnu/libstdc++.so.6           EXPORTS              _ZNSt8ios_base4InitC1Ev
/usr/lib/x86_64-linux-gnu/libstdc++.so.6           EXPORTS              _ZNSt8ios_base4InitD1Ev
/lib/x86_64-linux-gnu/libc.so.6                    EXPORTS              __cxa_finalize
/lib/x86_64-linux-gnu/libc.so.6                    EXPORTS              __cxa_atexit
/home/vanessa/Desktop/Code/elfcall/data/libfoo.so  NEEDS                __cxa_finalize
/home/vanessa/Desktop/Code/elfcall/data/libfoo.so  NEEDS                __cxa_atexit
/home/vanessa/Desktop/Code/elfcall/data/libfoo.so  NEEDS                _ZNSt8ios_base4InitC1Ev
/home/vanessa/Desktop/Code/elfcall/data/libfoo.so  NEEDS                _ZNSt8ios_base4InitD1Ev

For the above, we might be in trouble if the number of NEEDS didn’t equal the number of EXPORTS as we would be missing a symbol. To pipe to file:

$ elfcall gen data/libfoo.so --fmt text > data/examples/text/graph.txt

From within Python you might do:

from elfcall.main import BinaryInterface
cli = BinaryInterface("data/libfoo.so")
cli.gen(fmt="text")

cypher

Cypher is the query format for Neo4j, the graph database.

$ elfcall gen data/libfoo.so --fmt cypher
CREATE (omyaovuh:ELF {name: 'libfoo.so', label: 'libfoo.so'}),
(ilfrbqrc:ELF {name: 'libstdc++.so.6', label: 'libstdc++.so.6'}),
(vyiefgcr:ELF {name: 'libm.so.6', label: 'libm.so.6'}),
(gnxoyhkm:ELF {name: 'libc.so.6', label: 'libc.so.6'}),
(fvynaahi:ELF {name: 'ld-linux-x86-64.so.2', label: 'ld-linux-x86-64.so.2'}),
(hsrlkhie:ELF {name: 'libgcc_s.so.1', label: 'libgcc_s.so.1'}),
(kgyffmqn:SYMBOL {name: '__cxa_finalize', label: '__cxa_finalize', type: 'FUNC'}),
(bieoloch:SYMBOL {name: '_ZNSt8ios_base4InitC1Ev', label: '_ZNSt8ios_base4InitC1Ev', type: 'FUNC'}),
(owpwqsyl:SYMBOL {name: '__cxa_atexit', label: '__cxa_atexit', type: 'FUNC'}),
(stndoxns:SYMBOL {name: '_ZNSt8ios_base4InitD1Ev', label: '_ZNSt8ios_base4InitD1Ev', type: 'FUNC'}),
(omyaovuh)-[:LINKSWITH]->(ilfrbqrc),
(omyaovuh)-[:LINKSWITH]->(lxtmuvsv),
(ilfrbqrc)-[:LINKSWITH]->(vyiefgcr),
(ilfrbqrc)-[:LINKSWITH]->(gnxoyhkm),
(ilfrbqrc)-[:LINKSWITH]->(fvynaahi),
(ilfrbqrc)-[:LINKSWITH]->(hsrlkhie),
(lxtmuvsv)-[:LINKSWITH]->(fvynaahi),
(ilfrbqrc)-[:EXPORTS]->(bieoloch),
(ilfrbqrc)-[:EXPORTS]->(stndoxns),
(lxtmuvsv)-[:EXPORTS]->(kgyffmqn),
(lxtmuvsv)-[:EXPORTS]->(owpwqsyl),
(omyaovuh)-[:NEEDS]->(kgyffmqn),
(omyaovuh)-[:NEEDS]->(owpwqsyl),
(omyaovuh)-[:NEEDS]->(bieoloch),
(omyaovuh)-[:NEEDS]->(stndoxns);

What you are seeing above is a definition of node and relationships. You can pipe to file:

$ elfcall gen data/libfoo.so --fmt cypher > data/examples/cypher/graph.cypher
$ elfcall gen /usr/bin/vim --fmt cypher > data/examples/cypher/graph-vim.cypher

If you test the output in the Neo4J sandbox by first running the code to generate nodes and then doing:

MATCH (n) RETURN (n)

You should see:

Cypher Graph

From within Python you might do:

from elfcall.main import BinaryInterface
cli = BinaryInterface("data/libfoo.so")
cli.gen(fmt="cypher")

dot

Dot is probably one of my favorite because you can use the dot command line tool to make pretty beautiful plots!

$ elfcall gen data/libfoo.so --fmt dot

And here is how to generate a png or svg:

$ elfcall gen data/libfoo.so --fmt dot > data/examples/dot/graph.dot
$ dot -Tpng < data/examples/dot/graph.dot > data/examples/dot/graph.png
$ dot -Tsvg < data/examples/dot/graph.dot > data/examples/dot/graph.svg

That generates this beauty!

Dot Graph

Note that this format isn’t great for large graphs.

Gexf (NetworkX)

If you want to use networkX or Gephi or a viewer you can generate output as follows:

$ elfcall gen data/libfoo.so --fmt gexf
$ elfcall gen data/libfoo.so --fmt gexf > data/examples/gexf/graph.xml

To use the viewer, you’ll first need to import into Gephi so the nodes have added spatial information. Without this information, you won’t see them in the UI. You can then do the following:

$ here=$PWD
$ cd /tmp
$ git clone https://github.com/raphv/gexf-js
$ cd gexf-js

# The file we generated above, we copy over the example so we don't have
# to edit config.js
$ cp $here/data/examples/gexf/graph.xml miserables.gexf

And then run the server!

$ python -m http.server 9999

As an alternative, networkx can also read in the gexf file:

import matplotlib.pyplot as plt
import networkx as nx

graph = nx.read_gexf('data/examples/gexf/graph.xml')

nx.draw(graph, with_labels=True, font_weight='bold')
plt.show()

Tree

Elfcall also lets you generate a tree of the library paths parsed:

$ elfcall tree data/libfoo.so
libstdc++.so.6                 [x86_64-linux-gnu.conf]
   ld-linux-x86-64.so.2        [x86_64-linux-gnu.conf]
libm.so.6                      [x86_64-linux-gnu.conf]
libgcc_s.so.1                  [x86_64-linux-gnu.conf]
libc.so.6                      [x86_64-linux-gnu.conf]

Notice the right column, the source of finding a file? This is the source lookup I was talking about in Background. You can verify needed using readelf:

$ readelf -a data/libfoo.so | grep NEEDED
0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

and here is an example with vim:

$ elfcall tree /usr/bin/vim
libm.so.6                      [x86_64-linux-gnu.conf]
   ld-linux-x86-64.so.2        [x86_64-linux-gnu.conf]
libtinfo.so.6                  [x86_64-linux-gnu.conf]
libselinux.so.1                [x86_64-linux-gnu.conf]
   libpcre2-8.so.0             [x86_64-linux-gnu.conf]
libcanberra.so.0               [x86_64-linux-gnu.conf]
   libvorbisfile.so.3          [x86_64-linux-gnu.conf]
      libvorbis.so.0           [x86_64-linux-gnu.conf]
      libogg.so.0              [x86_64-linux-gnu.conf]
   libtdb.so.1                 [x86_64-linux-gnu.conf]
   libltdl.so.7                [x86_64-linux-gnu.conf]
libacl.so.1                    [x86_64-linux-gnu.conf]
libgpm.so.2                    [x86_64-linux-gnu.conf]
libdl.so.2                     [x86_64-linux-gnu.conf]
libpython3.8.so.1.0            [x86_64-linux-gnu.conf]
   libexpat.so.1               [x86_64-linux-gnu.conf]
   libz.so.1                   [x86_64-linux-gnu.conf]
   libutil.so.1                [x86_64-linux-gnu.conf]
libpthread.so.0                [x86_64-linux-gnu.conf]
libc.so.6                      [x86_64-linux-gnu.conf]

And the needed headers:

$ readelf -a /usr/bin/vim | grep NEEDED
0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libtinfo.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libselinux.so.1]
0x0000000000000001 (NEEDED)             Shared library: [libcanberra.so.0]
0x0000000000000001 (NEEDED)             Shared library: [libacl.so.1]
0x0000000000000001 (NEEDED)             Shared library: [libgpm.so.2]
0x0000000000000001 (NEEDED)             Shared library: [libdl.so.2]
0x0000000000000001 (NEEDED)             Shared library: [libpython3.8.so.1.0]
0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]