Skip to content

Commit

Permalink
fixing bug that we need to check elf header info to match libs.
Browse files Browse the repository at this point in the history
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
  • Loading branch information
vsoch committed Jan 21, 2022
1 parent 3f53779 commit 92db09c
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 25 deletions.
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ __cxa_atexit __cxa_finalize

```

The defaults above show the console. DIfferent formats are shown below (under development).

#### Text

```bash
$ elfcall gen data/libfoo.so --fmt text
/usr/lib/x86_64-linux-gnu/libstdc++.so.6 LINKSWITH libm.so.6
/usr/lib/x86_64-linux-gnu/libstdc++.so.6 LINKSWITH 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
/usr/lib/x86_64-linux-gnu/libstdc++.so.6 EXPORTS _ZNSt8ios_base4InitC1Ev
/usr/lib/x86_64-linux-gnu/libstdc++.so.6 EXPORTS _ZNSt8ios_base4InitD1Ev
```


Note that this is under development, and eventually we will have different graph generation
options (right now we print to the screen).

Expand All @@ -92,19 +107,13 @@ You can also generate a tree of the library paths parsed:
```bash
$ elfcall tree data/libfoo.so
libstdc++.so.6 [x86_64-linux-gnu.conf]
libm.so.6 [i386-linux-gnu.conf]
libc.so.6 [i386-linux-gnu.conf]
ld-linux.so.2 [i386-linux-gnu.conf]
ld-linux-x86-64.so.2 [x86_64-linux-gnu.conf]
libgcc_s.so.1 [i386-linux-gnu.conf]
ld-linux-x86-64.so.2 [x86_64-linux-gnu.conf]
```

or:

```bash
$ elfcall tree /usr/bin/vim
libm.so.6 [i386-linux-gnu.conf]
ld-linux.so.2 [i386-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]
Expand All @@ -117,13 +126,8 @@ libcanberra.so.0 [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 [i386-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 [i386-linux-gnu.conf]
libpthread.so.0 [i386-linux-gnu.conf]
libc.so.6 [i386-linux-gnu.conf]
```


Expand Down
8 changes: 8 additions & 0 deletions elfcall/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ def get_parser():
description="generate the symbol callgraph.",
formatter_class=argparse.RawTextHelpFormatter,
)
gen.add_argument(
"--fmt",
"-f",
help="graph format to generate",
choices=["text", "ge", "gexf", "console"],
default="console",
)

tree = subparsers.add_parser(
"tree",
description="generate a library tree",
Expand Down
2 changes: 1 addition & 1 deletion elfcall/client/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ def main(args, parser, extra, subparser):
if not args.binary:
logger.exit("You must provide one or more binaries to parse.")
cli = BinaryInterface(args.binary[0], quiet=args.quiet)
cli.gen()
cli.gen(fmt=args.fmt)
30 changes: 22 additions & 8 deletions elfcall/main/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,14 @@ def tree(self, binary=None):
self.reset()
self.ld.parse()

# TODO will we ever want to match:
# e_ident[EI_DATA], e_ident[EI_CLASS], e_ident[EI_OSABI], e_ident[EI_ABIVERSION],
# e_machine, e_type, e_flags and e_version.
# https://github.com/vsoch/elfcall/issues/1

results = self.recursive_find(binary)
# Load original binary - we need to match elf attributes here
original = elf.ElfFile(binary)
results = self.recursive_find(binary, original=original)
self.library_tree(results)

def recursive_find(self, lib, root=None, needed_search=None, seen=None, level=0):
def recursive_find(
self, lib, original, root=None, needed_search=None, seen=None, level=0
):
"""
recursively find needed paths, keep track of hierarchy
"""
Expand Down Expand Up @@ -113,6 +112,10 @@ def recursive_find(self, lib, root=None, needed_search=None, seen=None, level=0)

libelf, src, already_seen = self.find_library(path, search_paths)

# If it does not match the arch and elf type, ignore
if not original.matches(libelf):
continue

# We might get back a soname instead we've already seen
if already_seen:
continue
Expand Down Expand Up @@ -149,6 +152,7 @@ def recursive_find(self, lib, root=None, needed_search=None, seen=None, level=0)
root=next["root"],
needed_search=next["needed"],
level=next["level"],
original=original,
)

return root
Expand Down Expand Up @@ -257,6 +261,10 @@ def parse_binary(self, binary):
# This will return loaded ELF, if found, otherwise None
libelf, _, already_seen = self.find_library(path, search_paths)

# If it does not match the arch and elf type, ignore
if not e.matches(libelf):
continue

# We might get back a soname instead we've already seen
if already_seen:
continue
Expand All @@ -277,7 +285,13 @@ def parse_binary(self, binary):
for name, symbol in imported.items():
if name in exported_contenders:
logger.debug("Found %s -> %s" % (name, path))
found[name] = {"lib": lib, "linked_libs": libelf.needed}
found[name] = {
"lib": {
"realpath": libelf.realpath,
"fullpath": libelf.fullpath,
},
"linked_libs": libelf.needed,
}
found[name].update(symbol)
to_removes.append(name)
for to_remove in to_removes:
Expand Down
16 changes: 16 additions & 0 deletions elfcall/main/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ def needed(self):
self._needed.append(tag.needed)
return self._needed

def matches(self, libelf):
"""
Compare an elf to another lib's elf type, arch, and header metadata.
Return True if they match, False otherwise.
"""
# Note that including EI_OSABI
# e_ident[EI_DATA], e_ident[EI_CLASS], e_ident[EI_OSABI], e_ident[EI_ABIVERSION],
for field in ["EI_CLASS", "EI_DATA", "EI_ABIVERSION"]:
if self.header["e_ident"][field] != libelf.header["e_ident"][field]:
return False
# e_machine, e_type, e_flags and e_version.
for field in ["e_type", "e_machine", "e_flags", "e_version"]:
if self.header[field] != libelf.header[field]:
return False
return True

@property
def operating_system(self):
return self.header["e_ident"]["EI_OSABI"]
Expand Down
7 changes: 3 additions & 4 deletions elfcall/main/graph/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ def generate(self):

class Text(GraphBase):
def generate(self):
logger.info("Output will be written to %s" % self.outfile)
if self.outfile != sys.stdout:
logger.info("Output will be written to %s" % self.outfile)
fd = open(self.outfile, "w")
else:
fd = self.outfile

# Record linked dependencies
for filename, symbols in self.organized.items():
for linked_lib in self.linked_libs[filename]:
fd.write("%s LINKSWITH %s\n" % (filename, linked_lib))
fd.write("{:50} {:20} {}\n".format(filename, "LINKSWITH", linked_lib))

# We only care about each library imports (exported from another)
exported = self.get_exported()
Expand All @@ -40,14 +40,13 @@ def generate(self):
for symbol in exported:
placeholder = self.generate_placeholder()
self.symbol_uids[symbol[0]] = placeholder
fd.write("%s EXPORTS %s\n" % (filename, symbol[0]))

# store which files use which symbols
for filename, metas in self.organized.items():
for meta in metas:
symbol = meta["name"]
placeholder = self.symbol_uids[symbol]
fd.write("%s USES %s\n" % (filename, symbol))
fd.write("{:50} {:20} {}\n".format(filename, "EXPORTS", symbol))

if self.outfile != sys.stdout:
fd.close()

0 comments on commit 92db09c

Please sign in to comment.