Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
import unittest
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import stat
|
|
|
|
import re
|
|
|
|
import subprocess
|
|
|
|
import tempfile
|
|
|
|
import shutil
|
|
|
|
import lief
|
|
|
|
from lief import Logger
|
|
|
|
Logger.set_level(lief.LOGGING_LEVEL.INFO)
|
|
|
|
|
|
|
|
from subprocess import Popen
|
|
|
|
from unittest import TestCase
|
2019-08-22 22:05:53 +01:00
|
|
|
from utils import get_sample, get_compiler
|
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
|
|
|
|
|
|
|
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
STUB = lief.parse(os.path.join(CURRENT_DIRECTORY, "hello_lief.bin"))
|
|
|
|
|
|
|
|
LIBADD_C = """\
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
int add(int a, int b);
|
|
|
|
|
|
|
|
int add(int a, int b) {
|
|
|
|
printf("%d + %d = %d\\n", a, b, a + b);
|
|
|
|
return a + b;
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
BINADD_C = """\
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
int add(int a, int b);
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
if (argc != 3) {
|
|
|
|
printf("Usage: %s <a> <b>\\n", argv[0]);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int res = add(atoi(argv[1]), atoi(argv[2]));
|
|
|
|
printf("From myLIb, a + b = %d\\n", res);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
class LibAddSample(object):
|
|
|
|
COUNT = 0
|
|
|
|
def __init__(self):
|
|
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
self.tmp_dir = tempfile.mkdtemp(suffix='_lief_sample_{:d}'.format(LibAddSample.COUNT))
|
|
|
|
self.logger.debug("temp dir: {}".format(self.tmp_dir))
|
|
|
|
|
|
|
|
LibAddSample.COUNT += 1
|
|
|
|
|
|
|
|
self.binadd_path = os.path.join(self.tmp_dir, "binadd.c")
|
|
|
|
self.libadd_path = os.path.join(self.tmp_dir, "libadd.c")
|
|
|
|
|
|
|
|
self.libadd_so = os.path.join(self.tmp_dir, "libadd.so")
|
|
|
|
self.binadd_bin = os.path.join(self.tmp_dir, "binadd.bin")
|
|
|
|
|
2019-08-22 22:05:53 +01:00
|
|
|
self.compiler = get_compiler()
|
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
|
|
|
self.logger.debug("Compiler: {}".format(self.compiler))
|
|
|
|
|
|
|
|
with open(self.binadd_path, 'w') as f:
|
|
|
|
f.write(BINADD_C)
|
|
|
|
|
|
|
|
with open(self.libadd_path, 'w') as f:
|
|
|
|
f.write(LIBADD_C)
|
|
|
|
|
|
|
|
self._compile_libadd()
|
|
|
|
self._compile_binadd()
|
|
|
|
|
|
|
|
|
|
|
|
def _compile_libadd(self, extra_flags=[]):
|
|
|
|
if os.path.isfile(self.libadd_so):
|
|
|
|
os.remove(self.libadd_so)
|
|
|
|
|
|
|
|
CC_FLAGS = ['-fPIC', '-shared', '-Wl,-soname,libadd.so'] + extra_flags
|
|
|
|
cmd = [self.compiler, '-o', self.libadd_so] + CC_FLAGS + [self.libadd_path]
|
|
|
|
self.logger.debug("Compile 'libadd' with: {}".format(" ".join(cmd)))
|
|
|
|
|
|
|
|
p = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
|
|
|
|
stdout, _ = p.communicate()
|
|
|
|
|
|
|
|
self.logger.debug(stdout)
|
|
|
|
|
|
|
|
|
|
|
|
def _compile_binadd(self, extra_flags=[]):
|
|
|
|
if os.path.isfile(self.binadd_bin):
|
|
|
|
os.remove(self.binadd_bin)
|
|
|
|
|
|
|
|
CC_FLAGS = ['-L', self.tmp_dir] + extra_flags
|
|
|
|
cmd = [self.compiler, '-o', self.binadd_bin] + CC_FLAGS + [self.binadd_path, '-ladd']
|
|
|
|
self.logger.debug("Compile 'binadd' with: {}".format(" ".join(cmd)))
|
|
|
|
p = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
stdout, _ = p.communicate()
|
|
|
|
self.logger.debug(stdout)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def libadd(self):
|
|
|
|
return self.libadd_so
|
|
|
|
|
|
|
|
@property
|
|
|
|
def binadd(self):
|
|
|
|
return self.binadd_bin
|
|
|
|
|
|
|
|
@property
|
|
|
|
def directory(self):
|
|
|
|
return self.tmp_dir
|
|
|
|
|
|
|
|
def remove(self):
|
|
|
|
if os.path.isdir(self.directory):
|
|
|
|
shutil.rmtree(self.directory)
|
|
|
|
|
2017-09-12 13:57:51 +02:00
|
|
|
def __del__(self):
|
|
|
|
self.remove()
|
|
|
|
|
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
|
|
|
|
|
|
|
class TestDynamic(TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2017-09-12 15:37:32 +02:00
|
|
|
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
|
2017-09-12 13:57:51 +02:00
|
|
|
def test_remove_library(self):
|
|
|
|
sample = LibAddSample()
|
|
|
|
libadd = lief.parse(sample.libadd)
|
|
|
|
binadd = lief.parse(sample.binadd)
|
|
|
|
|
|
|
|
libadd_needed = binadd.get_library("libadd.so")
|
|
|
|
binadd -= libadd_needed
|
|
|
|
self.assertFalse(binadd.has_library("libadd.so"))
|
|
|
|
|
|
|
|
|
2017-09-12 15:37:32 +02:00
|
|
|
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
|
2017-09-12 13:57:51 +02:00
|
|
|
def test_remove_tag(self):
|
|
|
|
sample = LibAddSample()
|
|
|
|
libadd = lief.parse(sample.libadd)
|
|
|
|
binadd = lief.parse(sample.binadd)
|
|
|
|
self.logger.debug("BEFORE")
|
|
|
|
list(map(lambda e : self.logger.debug(e), binadd.dynamic_entries))
|
|
|
|
self.logger.debug("")
|
|
|
|
binadd -= lief.ELF.DYNAMIC_TAGS.NEEDED
|
|
|
|
|
|
|
|
self.logger.debug("AFTER")
|
|
|
|
list(map(lambda e : self.logger.debug(e), binadd.dynamic_entries))
|
|
|
|
self.logger.debug("")
|
|
|
|
|
|
|
|
self.assertTrue(all(e.tag != lief.ELF.DYNAMIC_TAGS.NEEDED for e in binadd.dynamic_entries))
|
|
|
|
|
2017-09-12 15:37:32 +02:00
|
|
|
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
|
2017-09-12 15:05:30 +02:00
|
|
|
def test_runpath_api(self):
|
|
|
|
sample = LibAddSample()
|
|
|
|
libadd = lief.parse(sample.libadd)
|
|
|
|
binadd = lief.parse(sample.binadd)
|
|
|
|
|
|
|
|
rpath = lief.ELF.DynamicEntryRunPath()
|
|
|
|
rpath = binadd.add(rpath)
|
|
|
|
self.logger.debug(rpath)
|
|
|
|
rpath += "/tmp"
|
|
|
|
|
|
|
|
self.logger.debug(rpath)
|
|
|
|
|
|
|
|
self.assertEqual(rpath.paths, ["/tmp"])
|
|
|
|
self.assertEqual(rpath.runpath, "/tmp")
|
|
|
|
|
|
|
|
rpath.insert(0, "/foo")
|
|
|
|
|
|
|
|
self.assertEqual(rpath.paths, ["/foo", "/tmp"])
|
|
|
|
self.assertEqual(rpath.runpath, "/foo:/tmp")
|
|
|
|
|
|
|
|
rpath.paths = ["/foo", "/tmp", "/bar"]
|
|
|
|
|
|
|
|
self.logger.debug(rpath)
|
|
|
|
|
|
|
|
self.assertEqual(rpath.paths, ["/foo", "/tmp", "/bar"])
|
|
|
|
self.assertEqual(rpath.runpath, "/foo:/tmp:/bar")
|
|
|
|
|
|
|
|
rpath -= "/tmp"
|
|
|
|
|
|
|
|
self.logger.debug(rpath)
|
|
|
|
|
|
|
|
self.assertEqual(rpath.runpath, "/foo:/bar")
|
|
|
|
|
|
|
|
rpath.remove("/foo").remove("/bar")
|
|
|
|
|
|
|
|
self.logger.debug(rpath)
|
|
|
|
|
|
|
|
self.assertEqual(rpath.runpath, "")
|
|
|
|
|
|
|
|
self.logger.debug(rpath)
|
|
|
|
|
|
|
|
|
2017-09-12 13:57:51 +02:00
|
|
|
|
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
|
|
|
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
|
|
|
|
def test_change_libname(self):
|
|
|
|
sample = LibAddSample()
|
|
|
|
libadd = lief.parse(sample.libadd)
|
|
|
|
binadd = lief.parse(sample.binadd)
|
|
|
|
|
|
|
|
new_name = "libwhichhasalongverylongname.so"
|
|
|
|
|
|
|
|
|
|
|
|
self.assertIn(lief.ELF.DYNAMIC_TAGS.SONAME, libadd)
|
|
|
|
|
|
|
|
so_name = libadd[lief.ELF.DYNAMIC_TAGS.SONAME]
|
|
|
|
self.logger.debug("DT_SONAME: {}".format(so_name.name))
|
|
|
|
so_name.name = new_name
|
|
|
|
|
|
|
|
libfoo_path = os.path.join(sample.directory, new_name)
|
|
|
|
self.logger.debug(libfoo_path)
|
|
|
|
libadd.write(libfoo_path)
|
|
|
|
|
|
|
|
libfoo = lief.parse(libfoo_path)
|
|
|
|
|
|
|
|
new_so_name = libadd[lief.ELF.DYNAMIC_TAGS.SONAME]
|
|
|
|
# Check builder did the job right
|
|
|
|
self.assertEqual(new_so_name.name, new_name)
|
|
|
|
|
|
|
|
libadd_needed = binadd.get_library("libadd.so")
|
|
|
|
libadd_needed.name = new_name
|
|
|
|
|
|
|
|
# Add a RPATH entry
|
2017-09-12 15:05:30 +02:00
|
|
|
rpath = lief.ELF.DynamicEntryRunPath(sample.directory)
|
Improve the ELF part of LIEF
Major changes (features):
* Enable adding multiple sections/segments - Executable (PIE or not), Library
* Enable adding multiple dynamic entries (DT_NEEDED, DT_INIT etc)
* Enable adding multiple relocations
* Enable adding multiple dynamic symbols
* Enable segment replacement
Major changes (API):
* Getters Binary::get_*name*() has been renamed to "name()"
* Binary::add(const DynamicEntry& entry) - To add an entry in the dynamic table
* Section& Binary::add(const Section& section, bool loaded = true) - To add a section(s)
* Segment& Binary::add(const Segment& segment, uint64_t base = 0) - To add segments
* Segment& replace(const Segment& new_segment, const Segment& original_segment, uint64_t base = 0)
* Binary's last_offset_section(), last_offset_segment(), next_virtual_address()
to have information about offset
* Binary's add_library(), get_library(), has_library() to handle
DT_NEEDED entries
Other changes:
* Binary::insert_content() - Use add(const Section&) or add(const Segment&) instead
* ELF's DataHandler has been cleaned
* Through LIEF::Section one can look for integers, strings, data
within the section (see LIEF::Section::search,
LIEF::Section::search_all)
* Through LIEF::Binary one can get *xref* of a number (or address)
see LIEF::Binary::xref function
* To access to the Abstract binary in Python, one can now use
the 'abstract' attribute. (e.g. binary.abstract.header.is_32)
Resolve: #83
Resolve: #66
Resolve: #48
2017-09-02 08:54:54 +02:00
|
|
|
rpath = binadd.add(rpath)
|
|
|
|
self.logger.debug(rpath)
|
|
|
|
|
|
|
|
new_binadd_path = os.path.join(sample.directory, "binadd_updated.bin")
|
|
|
|
self.logger.debug(new_binadd_path)
|
|
|
|
binadd.write(new_binadd_path)
|
|
|
|
|
|
|
|
# Remove original binaries:
|
|
|
|
os.remove(sample.libadd)
|
|
|
|
os.remove(sample.binadd)
|
|
|
|
|
|
|
|
# Run the new executable
|
|
|
|
st = os.stat(libfoo_path)
|
|
|
|
os.chmod(libfoo_path, st.st_mode | stat.S_IEXEC)
|
|
|
|
|
|
|
|
st = os.stat(new_binadd_path)
|
|
|
|
os.chmod(new_binadd_path, st.st_mode | stat.S_IEXEC)
|
|
|
|
|
|
|
|
p = Popen([new_binadd_path, '1', '2'],
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT)
|
|
|
|
#env={"LD_LIBRARY_PATH": sample.directory}) # Shouldn't be needed if RPATH inject succeed
|
|
|
|
stdout, _ = p.communicate()
|
|
|
|
if p.returncode > 0:
|
|
|
|
self.logger.fatal(stdout.decode("utf8"))
|
|
|
|
self.assertEqual(p.returncode, 0)
|
|
|
|
|
|
|
|
self.logger.debug(stdout.decode("utf8"))
|
|
|
|
self.assertIsNotNone(re.search(r'From myLIb, a \+ b = 3', stdout.decode("utf8")))
|
|
|
|
|
|
|
|
sample.remove()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
|
|
root_logger = logging.getLogger()
|
|
|
|
root_logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
ch = logging.StreamHandler()
|
|
|
|
ch.setLevel(logging.DEBUG)
|
|
|
|
root_logger.addHandler(ch)
|
|
|
|
|
|
|
|
unittest.main(verbosity=2)
|
|
|
|
|