diff options
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | src/analysis/disass/area.c | 153 | ||||
-rw-r--r-- | tests/format/elf/Makefile | 5 | ||||
-rw-r--r-- | tests/format/elf/overlapping_areas.asm | 115 | ||||
-rw-r--r-- | tests/format/elf/overlapping_areas.py | 62 |
5 files changed, 335 insertions, 12 deletions
@@ -1,5 +1,17 @@ 17-05-11 Cyrille Bagard <nocbos@gmail.com> + * src/analysis/disass/area.c: + Handle preloaded instructions located in two cut areas (ELF overlapping). + + * tests/format/elf/Makefile: + Add overlapping_areas to EXECUTABLES and define rules to build the binary. + + * tests/format/elf/overlapping_areas.asm: + * tests/format/elf/overlapping_areas.py: + New entries: extend the test suite. + +17-05-11 Cyrille Bagard <nocbos@gmail.com> + * plugins/readelf/strtab.c: Handle out of bound string section length (as suggested by the test suite). diff --git a/src/analysis/disass/area.c b/src/analysis/disass/area.c index f4088e4..352069c 100644 --- a/src/analysis/disass/area.c +++ b/src/analysis/disass/area.c @@ -80,7 +80,10 @@ static bool is_range_empty_in_mem_area(mem_area *, phys_t, phys_t); static bool is_range_busy_in_mem_area(mem_area *, phys_t, phys_t); /* Marque une série d'octets comme ayant été traités. */ -static bool mark_range_in_mem_area_as_processed(mem_area *, GArchInstruction *, bool); +static bool mark_range_in_mem_area_as_processed(mem_area *, GArchInstruction *, bool, bool); + +/* Marque une série d'octets comme ayant été traités. */ +static void mark_range_in_mem_area_as_overlapping(mem_area *, phys_t); /* Crée une instruction issue d'un désassemblage brut. */ static GArchInstruction *load_raw_instruction_from_mem_area(mem_area *, phys_t, vmpa2t *, phys_t *); @@ -347,9 +350,10 @@ static bool is_range_busy_in_mem_area(mem_area *area, phys_t start, phys_t len) /****************************************************************************** * * -* Paramètres : area = aire représentant à contenu à parcourir. * -* instr = instruction à mémoriser pour la suite. * -* force = impose l'enregistrement de l'instruction. * +* Paramètres : area = aire représentant à contenu à parcourir. * +* instr = instruction à mémoriser pour la suite. * +* force = impose l'enregistrement de l'instruction. * +* overlap = indique si l'instruction peut déborder de la zone. * * * * Description : Marque une série d'octets comme ayant été traités. * * * @@ -359,7 +363,7 @@ static bool is_range_busy_in_mem_area(mem_area *area, phys_t start, phys_t len) * * ******************************************************************************/ -static bool mark_range_in_mem_area_as_processed(mem_area *area, GArchInstruction *instr, bool force) +static bool mark_range_in_mem_area_as_processed(mem_area *area, GArchInstruction *instr, bool force, bool overlap) { bool result; /* Bilan d'action à renvoyer */ const vmpa2t *start; /* Adresse de départ de la zone*/ @@ -382,6 +386,36 @@ static bool mark_range_in_mem_area_as_processed(mem_area *area, GArchInstruction offset = compute_vmpa_diff(start, addr); + /** + * On vérifie que l'instruction tient toute entière dans la zone courante. + * + * C'est normalement forcément le cas, puisque les accès aux données lues + * pour la mise en place d'une instruction sont restreintes à la zone couverte. + * + * Cependant ce n'est pas forcément le cas lors de l'injection des + * instructions préchargées. En effet, une chaîne de caractères peut être + * découpée sur plusieurs zones : il arrive par exemple que le segment BSS + * englobe une partie de la section des chaînes. Cette section est donc coupée + * en deux portions, ce qui conduit à la création de deux zones distinctes. + * + * Dans ce cas, on inscrit ici l'instruction résultante, et charge à + * l'appelant de marquer la zone suivante comme déjà partiellement traitée. + * + * Cette situation est représentée par le test 'overlapping_areas.py'. + */ + + if ((offset + len) > get_mrange_length(&area->range)) + { + if (overlap) + len = get_mrange_length(&area->range) - offset; + +#ifndef NDEBUG + else + assert(false); +#endif + + } + /* Début des choses sérieuses */ g_mutex_lock(&area->mutex); @@ -461,6 +495,59 @@ static bool mark_range_in_mem_area_as_processed(mem_area *area, GArchInstruction /****************************************************************************** * * +* Paramètres : area = aire représentant à contenu à parcourir. * +* count = nombre d'octets à considérer comme traités. * +* * +* Description : Marque une série d'octets comme ayant été traités. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void mark_range_in_mem_area_as_overlapping(mem_area *area, phys_t count) +{ + bool status; /* Présence d'instructions ? */ + phys_t i; /* Boucle de parcours */ + GArchInstruction *old; /* Instruction remplacée */ + const mrange_t *old_range; /* Emplacement de l'instruction*/ + phys_t old_len; /* Taille de cette instruction */ + + assert(count <= get_mrange_length(&area->range)); + + g_mutex_lock(&area->mutex); + + status = test_none_in_bit_field(area->processed, 0, count); + + if (!status) + for (i = 0; i < count; i++) + { + old = area->instructions[i]; + + if (old != NULL) + { + old_range = g_arch_instruction_get_range(old); + old_len = get_mrange_length(old_range); + + reset_in_bit_field(area->processed, i, old_len); + + g_object_unref(G_OBJECT(old)); + area->instructions[i] = NULL; + + g_atomic_pointer_add(&area->count, -1); + + } + + } + + g_mutex_unlock(&area->mutex); + +} + + +/****************************************************************************** +* * * Paramètres : area = aire représentant à contenu à parcourir. * * offset = point de départ au sein de l'aire en question. * * pos = tête de lecture dans l'espace global. * @@ -705,7 +792,7 @@ void load_code_from_mem_area(mem_area *area, mem_area *list, size_t count, GProc /* Progression dans les traitements */ - done = mark_range_in_mem_area_as_processed(area, instr, false); + done = mark_range_in_mem_area_as_processed(area, instr, false, false); if (!done) { @@ -807,7 +894,7 @@ static void load_data_from_mem_area(mem_area *area, GProcContext *ctx, const vmp /* Progression dans les traitements */ - done = mark_range_in_mem_area_as_processed(area, instr, false); + done = mark_range_in_mem_area_as_processed(area, instr, false, false); if (!done) { @@ -1035,10 +1122,10 @@ static void insert_extra_instr_into_mem_areas(mem_area *areas, size_t count, GAr /* Inscription d'une instruction (sans retour arrière possible :/ ) */ #ifndef NDEBUG - status = mark_range_in_mem_area_as_processed(area, instr, true); + status = mark_range_in_mem_area_as_processed(area, instr, true, false); assert(status); #else - mark_range_in_mem_area_as_processed(area, instr, true); + mark_range_in_mem_area_as_processed(area, instr, true, false); #endif return; @@ -1577,6 +1664,11 @@ static void g_area_collector_do_insert(GAreaCollector *collector, GtkStatusStack #ifndef NDEBUG bool inserted; /* Validation d'une insertion */ #endif + vmpa2t iend; /* Position finale nominale */ + vmpa2t pos; /* Tête de lecture */ + phys_t remaining; /* Zone supplémentaire couverte*/ + phys_t available; /* Zone disponible */ + phys_t mark_len; /* Zone à marquer comme traitée*/ area = NULL; @@ -1598,12 +1690,51 @@ static void g_area_collector_do_insert(GAreaCollector *collector, GtkStatusStack assert(area != NULL); #ifndef NDEBUG - inserted = mark_range_in_mem_area_as_processed(area, instr, false); + inserted = mark_range_in_mem_area_as_processed(area, instr, false, true); assert(inserted); #else - mark_range_in_mem_area_as_processed(area, instr, false); + mark_range_in_mem_area_as_processed(area, instr, false, true); #endif + /** + * Si l'instruction n'a pas pu être accueillie dans son intégralité, on + * ne rassemble pas les zones pour autant. + * + * On pourrait imaginer sauver la situation pour les segments débordant + * sur une section, mais le problème resterait posé pour des zones + * définies par l'utilisateur au sein d'une section. + * + * On note donc la place occupée par le débordement éventuel ici. + */ + + compute_mrange_end_addr(g_arch_instruction_get_range(instr), &iend); + compute_mrange_end_addr(&area->range, &pos); + + if (cmp_vmpa(&iend, &pos) > 0) + { + remaining = compute_vmpa_diff(&pos, &iend); + + while (remaining > 0) + { + area = find_memory_area_by_addr(collector->areas, collector->available, &pos); + assert(area != NULL); + + available = get_mrange_length(&area->range); + + if (remaining > available) + mark_len = available; + else + mark_len = remaining; + + mark_range_in_mem_area_as_overlapping(area, mark_len); + + advance_vmpa(&pos, mark_len); + remaining -= mark_len; + + } + + } + gtk_status_stack_update_activity_value(status, collector->id, 1); } diff --git a/tests/format/elf/Makefile b/tests/format/elf/Makefile index c32392f..8695bb1 100644 --- a/tests/format/elf/Makefile +++ b/tests/format/elf/Makefile @@ -1,11 +1,14 @@ -EXECUTABLES=oob_section_name +EXECUTABLES=oob_section_name overlapping_areas all: $(EXECUTABLES) oob_section_name: oob_section_name.o $(ARM_CROSS)objcopy $< -O binary $@ +overlapping_areas: overlapping_areas.o + $(ARM_CROSS)objcopy $< -O binary $@ + %.o: %.asm $(ARM_CROSS)as -c $< -o $@ diff --git a/tests/format/elf/overlapping_areas.asm b/tests/format/elf/overlapping_areas.asm new file mode 100644 index 0000000..debcca1 --- /dev/null +++ b/tests/format/elf/overlapping_areas.asm @@ -0,0 +1,115 @@ + +.macro bump addr + .word \addr + 0x200000 +.endm + +.macro label_offset lbl + .word \lbl - str_table +.endm + + +elf_header: + + .byte 0x7F, 'E', 'L', 'F' @ e_ident + .byte 1 @ EI_CLASS => ELFCLASS32 + .byte 1 @ EI_DATA => ELFDATA2LSB + .byte 1 @ EI_VERSION => EV_CURRENT + .byte 0 @ EI_OSABI => ELFOSABI_SYSV + .byte 0 @ EI_ABIVERSION + + .word 0 + .short 0 + .byte 0 + + .short 2 @ e_type => ET_EXEC + .short 40 @ e_machine => EM_ARM + .word 1 @ e_version => EV_CURRENT + bump main @ e_entry + + .word program_headers @ e_phoff + .word section_headers @ e_shoff + + .word 0x80 @ e_flags => EF_ARM_NEW_ABI + + .short 52 @ e_ehsize + .short 32 @ e_phentsize + .short 2 @ e_phnum + .short 40 @ e_shentsize + .short 2 @ e_shnum + .short 1 @ e_shstrndx + + +program_headers: + + .word 1 @ p_type => PT_LOAD + .word O @ p_offset + .word 0x200000 @ p_vaddr + .word 0x200000 @ p_paddr + .word bss_start @ p_filesz + .word bss_start @ p_memsz + .word 0x5 @ p_flags => PF_X | PF_R + .word 0x1000 @ p_align + + .word 1 @ p_type => PT_LOAD + .word bss_start @ p_offset + .word 0x300000 @ p_vaddr + .word 0x300000 @ p_paddr + .word bss_end - bss_start @ p_filesz + .word bss_end - bss_start @ p_memsz + .word 0x6 @ p_flags => PF_W | PF_R + .word 0x1 @ p_align + + +section_headers: + + label_offset text_lbl @ sh_name + .word 1 @ sh_type => SHT_PROGBITS + .word 0x6 @ sh_flags => SHF_ALLOC | SHF_EXECINSTR + bump main @ sh_addr + .word main @ sh_offset + .word main_return - main @ sh_size + .word 0 @ sh_link + .word 0 @ sh_info + .word 4 @ sh_addralign + .word 0 @ sh_entsize + + label_offset strtab_lbl @ sh_name + .word 3 @ sh_type => SHT_STRTAB + .word 0x0 @ sh_flags + .word 0x0 @ sh_addr + .word str_table @ sh_offset + .word str_table_end - str_table @ sh_size + .word 0 @ sh_link + .word 0 @ sh_info + .word 1 @ sh_addralign + .word 0 @ sh_entsize + + +main: + mov r7, #1 @ __NR_exit + mov r0, #42 @ $? + svc 0 + +main_return: + + +bss_start: + + .word 0x0 + .word 0x0 + .word 0x0 + .word 0x0 + +str_table: + + .byte 0, 0 +text_lbl: + .byte '.', 't', 'e', 'x', 't', 0 +strtab_lbl: + .byte '.', 's', 't', 'r', 't', 'a', 'b', 0 +blabla: + .byte 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A' +bss_end: + .byte 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 0 + +str_table_end: diff --git a/tests/format/elf/overlapping_areas.py b/tests/format/elf/overlapping_areas.py new file mode 100644 index 0000000..4c78625 --- /dev/null +++ b/tests/format/elf/overlapping_areas.py @@ -0,0 +1,62 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + + +# Il arrive que les segments englobent partiellement des sections. +# +# Cela peut être problématique si une section contient une chaîne de taille +# n qui se retrouve à cheval sur deux zones (la section des chaînes découpée +# en deux par exemple). +# +# Au moment d'associer l'instruction chargée à la zone de départ, cette +# dernière n'est pas assez grande car elle ne représente pas la section +# en entier. + + +from chrysacase import ChrysalideTestCase +from pychrysalide.analysis import LoadedBinary +from pychrysalide.analysis.contents import FileContent +from threading import Event +import os +import sys + + +class TestOverlappingAreas(ChrysalideTestCase): + """TestCase for BSS segment overlapping string section.""" + + @classmethod + def setUpClass(cls): + + super(TestOverlappingAreas, cls).setUpClass() + + cls.log('Compile binary "overlapping_areas" if needed...') + + fullname = sys.modules[cls.__module__].__file__ + dirpath = os.path.dirname(fullname) + + os.system('make -C %s overlapping_areas 2>&1 > /dev/null' % dirpath) + + + def testOOBSectionName(self): + """Avoid crashing because of overlapping binary areas.""" + + fullname = sys.modules[self.__class__.__module__].__file__ + filename = os.path.basename(fullname) + + baselen = len(fullname) - len(filename) + + cnt = FileContent(fullname[:baselen] + 'overlapping_areas') + self.assertIsNotNone(cnt) + + binary = LoadedBinary(cnt) + + def disass_done(binary): + worker.set() + + binary.connect('disassembly-done', disass_done) + + worker = Event() + + binary.analyse() + + worker.wait() |