├── heaphopper ├── utils │ ├── __init__.py │ ├── angr_tools.py │ ├── parse_config.py │ └── input.py ├── analysis │ ├── tracer │ │ ├── __init__.py │ │ └── tracer.py │ ├── identify_bins │ │ ├── __init__.py │ │ ├── identify.c │ │ ├── Makefile │ │ └── identifier.py │ ├── __init__.py │ ├── concretizer.py │ ├── mem_limiter.py │ ├── vuln_checker.py │ └── heap_condition_tracker.py ├── __init__.py └── gen │ ├── __init__.py │ ├── gen_zoo.py │ └── gen_pocs.py ├── overview.png ├── tests ├── .gitignore ├── libc-2.23 │ ├── libc.so.6 │ └── ld-linux-x86-64.so.2 ├── libc-2.27 │ ├── libc.so.6 │ ├── libc.so.6.i64 │ └── ld-linux-x86-64.so.2 ├── how2heap_fastbin_dup │ ├── fastbin_dup.bin │ ├── fastbin_dup.desc │ ├── fastbin_dup.c │ └── analysis.yaml ├── how2heap_house_of_lore │ ├── house_of_lore.bin │ ├── house_of_lore.desc │ ├── analysis.yaml │ └── house_of_lore.c ├── how2heap_unsafe_unlink │ ├── unsafe_unlink.bin │ ├── unsafe_unlink.desc │ ├── unsafe_unlink.c │ └── analysis.yaml ├── how2heap_house_of_spirit │ ├── house_of_spirit.bin │ ├── house_of_spirit.desc │ ├── house_of_spirit.c │ └── analysis.yaml ├── how2heap_poison_null_byte │ ├── poison_null_byte.bin │ ├── poison_null_byte.desc │ ├── analysis.yaml │ └── poison_null_byte.c ├── how2heap_tcache_poisoning │ ├── tcache_poisoning.bin │ ├── tcache_poisoning.desc │ ├── tcache_poisoning.c │ └── analysis.yaml ├── how2heap_house_of_einherjar │ ├── house_of_einherjar.bin │ ├── house_of_einherjar.desc │ ├── house_of_einherjar.c │ └── analysis.yaml ├── how2heap_overlapping_chunks │ ├── overlapping_chunks.bin │ ├── overlapping_chunks_2.bin │ ├── overlapping_chunks.desc │ ├── overlapping_chunks.c │ ├── analysis.yaml │ ├── analysis_2.yaml │ └── overlapping_chunks_2.c ├── how2heap_unsorted_bin_attack │ ├── unsorted_bin_attack.bin │ ├── unsorted_bin_attack.desc │ ├── unsorted_bin_attack.c │ └── analysis.yaml ├── run_poc.sh ├── README.md ├── Makefile ├── analysis.yaml └── test_heaphopper.py ├── .gitignore ├── .github ├── workflows │ ├── ci.yml │ └── nightly-ci.yml └── ISSUE_TEMPLATE │ ├── config.yml │ ├── question.yml │ ├── feature-request.yml │ └── bug-report.yml ├── setup.py ├── LICENSE ├── README.md ├── heaphopper_client.py └── analysis.yaml /heaphopper/utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /heaphopper/analysis/tracer/__init__.py: -------------------------------------------------------------------------------- 1 | from .tracer import * 2 | -------------------------------------------------------------------------------- /heaphopper/__init__.py: -------------------------------------------------------------------------------- 1 | from .analysis import * 2 | from .gen import * 3 | -------------------------------------------------------------------------------- /heaphopper/analysis/identify_bins/__init__.py: -------------------------------------------------------------------------------- 1 | from .identifier import * 2 | -------------------------------------------------------------------------------- /overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/overview.png -------------------------------------------------------------------------------- /heaphopper/gen/__init__.py: -------------------------------------------------------------------------------- 1 | from .gen_pocs import * 2 | from .gen_zoo import * 3 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.oj 2 | *result.yaml 3 | *desc.yaml 4 | *_test.txt 5 | pocs/ 6 | -------------------------------------------------------------------------------- /heaphopper/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from .tracer import * 2 | from .identify_bins import * 3 | -------------------------------------------------------------------------------- /tests/libc-2.23/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/libc-2.23/libc.so.6 -------------------------------------------------------------------------------- /tests/libc-2.27/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/libc-2.27/libc.so.6 -------------------------------------------------------------------------------- /tests/libc-2.27/libc.so.6.i64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/libc-2.27/libc.so.6.i64 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | heaphopper/analysis/identify_bins/identify 4 | 5 | \.idea/ 6 | 7 | heaphopper\.egg-info/ 8 | -------------------------------------------------------------------------------- /tests/libc-2.23/ld-linux-x86-64.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/libc-2.23/ld-linux-x86-64.so.2 -------------------------------------------------------------------------------- /tests/libc-2.27/ld-linux-x86-64.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/libc-2.27/ld-linux-x86-64.so.2 -------------------------------------------------------------------------------- /tests/how2heap_fastbin_dup/fastbin_dup.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_fastbin_dup/fastbin_dup.bin -------------------------------------------------------------------------------- /tests/how2heap_house_of_lore/house_of_lore.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_house_of_lore/house_of_lore.bin -------------------------------------------------------------------------------- /tests/how2heap_unsafe_unlink/unsafe_unlink.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_unsafe_unlink/unsafe_unlink.bin -------------------------------------------------------------------------------- /tests/how2heap_house_of_spirit/house_of_spirit.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_house_of_spirit/house_of_spirit.bin -------------------------------------------------------------------------------- /tests/how2heap_poison_null_byte/poison_null_byte.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_poison_null_byte/poison_null_byte.bin -------------------------------------------------------------------------------- /tests/how2heap_tcache_poisoning/tcache_poisoning.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_tcache_poisoning/tcache_poisoning.bin -------------------------------------------------------------------------------- /tests/how2heap_house_of_einherjar/house_of_einherjar.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_house_of_einherjar/house_of_einherjar.bin -------------------------------------------------------------------------------- /tests/how2heap_overlapping_chunks/overlapping_chunks.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_overlapping_chunks/overlapping_chunks.bin -------------------------------------------------------------------------------- /tests/how2heap_overlapping_chunks/overlapping_chunks_2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_overlapping_chunks/overlapping_chunks_2.bin -------------------------------------------------------------------------------- /tests/how2heap_unsorted_bin_attack/unsorted_bin_attack.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/heaphopper/HEAD/tests/how2heap_unsorted_bin_attack/unsorted_bin_attack.bin -------------------------------------------------------------------------------- /tests/run_poc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -lt 1 ]; 4 | then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | LD_PRELOAD=./libc.so.6 ./ld-linux-x86-64.so.2 $1 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci: 12 | uses: angr/ci-settings/.github/workflows/angr-ci.yml@master 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Join our Slack community 4 | url: https://angr.io/invite/ 5 | about: For questions and help with angr, you are invited to join the angr Slack community 6 | -------------------------------------------------------------------------------- /heaphopper/analysis/identify_bins/identify.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | size_t malloc_sizes; 6 | 7 | void main(void) { 8 | 9 | void *ptr = malloc(malloc_sizes); 10 | 11 | free(ptr); 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/nightly-ci.yml: -------------------------------------------------------------------------------- 1 | name: Nightly CI 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | ci: 10 | uses: angr/ci-settings/.github/workflows/angr-ci.yml@master 11 | with: 12 | nightly: true 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_spirit/house_of_spirit.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"]], "fake_frees": ["sym_data"], "single_bitflips": [], "frees": [], "overflows": [], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_size[0]"]], "double_frees": [], "uafs": []} 2 | -------------------------------------------------------------------------------- /heaphopper/analysis/identify_bins/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all, clean, distclean 2 | 3 | BINARYS = identify 4 | all: $(BINARYS) 5 | 6 | identify: identify.o 7 | gcc -o $@ $^ -no-pie 8 | 9 | %.o: %.c 10 | gcc -O0 -c $^ 11 | 12 | clean: 13 | rm -rf *.o 14 | 15 | distclean: clean 16 | rm -rf $(BINARYS) 17 | -------------------------------------------------------------------------------- /tests/how2heap_unsafe_unlink/unsafe_unlink.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"]], "fake_frees": [], "single_bitflips": [], "frees": ["ctrl_data_1.global_var"], "overflows": [["ctrl_data_0.global_var", "ctrl_data_1.global_var", "overflow_sizes[0]"]], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_size"], ["ctrl_data_1.global_var", "fill_size"]], "double_frees": [], "uafs": []} 2 | -------------------------------------------------------------------------------- /tests/how2heap_tcache_poisoning/tcache_poisoning.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"], ["ctrl_data_2.global_var", "malloc_sizes[2]"]], "fake_frees": [], "single_bitflips": [], "frees": ["ctrl_data_0.global_var"], "overflows": [], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_sizes[0]"], ["ctrl_data_1.global_var", "fill_sizes[1]"], ["ctrl_data_2.global_var", "fill_sizes[2]"]], "double_frees": [], "uafs": [["ctrl_data_0.global_var", "header_size"]]} 2 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_einherjar/house_of_einherjar.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"], ["ctrl_data_2.global_var", "malloc_sizes[2]"]], "fake_frees": [], "single_bitflips": [], "frees": ["ctrl_data_0.global_var"], "overflows": [["ctrl_data_0.global_var", "ctrl_data_0.global_var", "overflow_sizes[0]"]], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_sizes[0]"], ["ctrl_data_1.global_var", "fill_sizes[1]"], ["ctrl_data_2.global_var", "fill_sizes[2]"]], "double_frees": [], "uafs": []} 2 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Test suite for the HeapHopper tool 2 | 3 | ## Currently working tests 4 | 5 | * fastbin_dup 6 | * house_of_einherjar 7 | * house_of_lore 8 | * house_of_spirit 9 | * unsafe_unlink 10 | * overlapping_chunks 11 | * unsorted_bin_attack 12 | * poison_null_byte 13 | 14 | ## WIP test 15 | 16 | * house_of_orange 17 | 18 | ## Not doable right now 19 | 20 | * house_of_force (needs specific allocation sizes) 21 | 22 | ## How to run 23 | 24 | `python run_tests.py` 25 | 26 | * Results will be stored in `*date*_test.txt` 27 | -------------------------------------------------------------------------------- /heaphopper/utils/angr_tools.py: -------------------------------------------------------------------------------- 1 | import select 2 | import sys 3 | 4 | from angr import SimPacketsStream 5 | 6 | 7 | def heardEnter(): 8 | if sys.stdin is not None: 9 | i, o, e = select.select([sys.stdin], [], [], 0.0001) 10 | for s in i: 11 | if s == sys.stdin: 12 | sys.stdin.readline() 13 | return True 14 | return False 15 | 16 | 17 | def all_bytes(file): 18 | if type(file) == SimPacketsStream: 19 | return file.content 20 | max_size = file.state.solver.max(file.size) 21 | return file.load(0, max_size) 22 | 23 | -------------------------------------------------------------------------------- /tests/how2heap_fastbin_dup/fastbin_dup.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"], ["ctrl_data_2.global_var", "malloc_sizes[2]"], ["ctrl_data_3.global_var", "malloc_sizes[3]"]], "fake_frees": [], "single_bitflips": [], "frees": ["ctrl_data_0.global_var"], "overflows": [], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_sizes[0]"], ["ctrl_data_1.global_var", "fill_sizes[1]"], ["ctrl_data_2.global_var", "fill_sizes[2]"], ["ctrl_data_3.global_var", "fill_sizes[3]"]], "double_frees": [], "uafs": [["ctrl_data_0.global_var", "header_size"]]} 2 | -------------------------------------------------------------------------------- /tests/how2heap_overlapping_chunks/overlapping_chunks.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"], ["ctrl_data_2.global_var", "malloc_sizes[2]"], ["ctrl_data_3.global_var", "malloc_sizes[3]"]], "fake_frees": [], "single_bitflips": [], "frees": ["ctrl_data_1.global_var"], "overflows": [["ctrl_data_0.global_var", "ctrl_data_1.global_var", "overflow_sizes[0]"]], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_sizes[0]"], ["ctrl_data_1.global_var", "fill_sizes[1]"], ["ctrl_data_2.global_var", "fill_sizes[2]"], ["ctrl_data_3.global_var", "fill_sizes[3]"]], "double_frees": [], "uafs": []} 2 | -------------------------------------------------------------------------------- /tests/how2heap_unsorted_bin_attack/unsorted_bin_attack.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"], ["ctrl_data_2.global_var", "malloc_sizes[2]"], ["ctrl_data_3.global_var", "malloc_sizes[3]"]], "fake_frees": [], "single_bitflips": [], "frees": [], "overflows": [["ctrl_data_0.global_var", "ctrl_data_1.global_var", "overflow_sizes[0]"]], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_size"], ["ctrl_data_1.global_var", "fill_size"], ["ctrl_data_2.global_var", "fill_size"], ["ctrl_data_3.global_var", "fill_size"]], "double_frees": [], "uafs": [["ctrl_data_0.global_var", "header_size"]]} 2 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_lore/house_of_lore.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"], ["ctrl_data_2.global_var", "malloc_sizes[2]"], ["ctrl_data_3.global_var", "malloc_sizes[3]"], ["ctrl_data_4.global_var", "malloc_sizes[4]"]], "fake_frees": [], "single_bitflips": [], "frees": ["ctrl_data_0.global_var"], "overflows": [], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_sizes[0]"], ["ctrl_data_1.global_var", "fill_sizes[1]"], ["ctrl_data_2.global_var", "fill_sizes[1]"], ["ctrl_data_3.global_var", "fill_sizes[3]"], ["ctrl_data_4.global_var", "fill_sizes[4]"]], "double_frees": [], "uafs": [["ctrl_data_0.global_var", "header_size"]]} 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | #scripts=['heaphopper_client.py'], 4 | try: 5 | from setuptools import setup 6 | from setuptools import find_packages 7 | packages = find_packages() 8 | except ImportError: 9 | from distutils.core import setup 10 | packages = [x.strip('./').replace('/','.') for x in os.popen('find -name "__init__.py" | xargs -n1 dirname').read().strip().split('\n')] 11 | 12 | setup( 13 | name='heaphopper', 14 | version='1.0', 15 | description='The HeapHopper', 16 | url='https://github/angr/heaphopper', 17 | packages=packages, 18 | install_requires=[ 19 | 'ana', 20 | 'angr', 21 | 'psutil', 22 | 'pyyaml', 23 | 'pyelftools', 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /tests/how2heap_poison_null_byte/poison_null_byte.desc: -------------------------------------------------------------------------------- 1 | {"allocs": [["ctrl_data_0.global_var", "malloc_sizes[0]"], ["ctrl_data_1.global_var", "malloc_sizes[1]"], ["ctrl_data_2.global_var", "malloc_sizes[2]"], ["ctrl_data_3.global_var", "malloc_sizes[3]"], ["ctrl_data_4.global_var", "malloc_sizes[4]"], ["ctrl_data_5.global_var", "malloc_sizes[5]"]], "fake_frees": [], "single_bitflips": [], "frees": ["ctrl_data_1.global_var", "ctrl_data_3.global_var", "ctrl_data_2.global_var"], "overflows": [["ctrl_data_0.global_var", "ctrl_data_1.global_var", "overflow_sizes[0]"]], "arb_relative_writes": [], "reads": [["ctrl_data_0.global_var", "fill_size"], ["ctrl_data_1.global_var", "fill_size"], ["ctrl_data_2.global_var", "fill_size"], ["ctrl_data_3.global_var", "fill_size"], ["ctrl_data_4.global_var", "fill_size"], ["ctrl_data_5.global_var", "fill_size"]], "double_frees": [], "uafs": []} 2 | -------------------------------------------------------------------------------- /heaphopper/utils/parse_config.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import os 3 | 4 | def parse_config(config_file): 5 | config = yaml.load(config_file, Loader=yaml.SafeLoader) 6 | for k, v in config.items(): 7 | if k in ['libc', 'allocator', 'loader']: 8 | v = os.path.abspath(os.path.join(os.path.dirname(config_file.name), v)) 9 | config[k] = v 10 | if 'global_config' in config: 11 | global_config_path = os.path.abspath(os.path.join(os.path.dirname(config_file.name), config['global_config'])) 12 | with open(global_config_path) as f: 13 | base = yaml.load(f, Loader=yaml.SafeLoader) 14 | for k, v in base.items(): 15 | if k not in config: 16 | if k in ['libc', 'allocator', 'loader']: 17 | v = os.path.abspath(os.path.join(os.path.dirname(global_config_path), v)) 18 | config[k] = v 19 | # Insert parsing here if necessary 20 | return config 21 | -------------------------------------------------------------------------------- /heaphopper/analysis/concretizer.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import logging 3 | 4 | logger = logging.getLogger('Concretizer') 5 | 6 | 7 | class Concretizer(angr.exploration_techniques.ExplorationTechnique): 8 | def __init__(self, addrs): 9 | super(Concretizer, self).__init__() 10 | self.addrs = addrs 11 | 12 | def step(self, simgr, stash, **kwargs): 13 | for addr in self.addrs: 14 | for s in simgr.active: 15 | var = s.memory.load(addr, s.arch.bits // s.arch.byte_width, endness="Iend_LE") 16 | if not var.symbolic: 17 | return simgr.step(stash=stash) 18 | 19 | vals = s.solver.eval_upto(var, 2) 20 | if len(vals) == 1: 21 | new_var = s.solver.BVV(vals[0], s.arch.bits) 22 | s.memory.store(addr, new_var, endness="Iend_LE") 23 | logger.info('Concretized {} @ {} to {}'.format(var, hex(addr), hex(vals[0]))) 24 | 25 | return simgr.step(stash=stash) 26 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all, clean 2 | CC = gcc 3 | CFLAGS += -std=c99 -g -O0 4 | LDFLAGS += -no-pie 5 | 6 | BINS = how2heap_fastbin_dup/fastbin_dup.bin how2heap_house_of_einherjar/house_of_einherjar.bin 7 | BINS += how2heap_house_of_lore/house_of_lore.bin how2heap_house_of_spirit/house_of_spirit.bin 8 | BINS += how2heap_overlapping_chunks/overlapping_chunks.bin how2heap_overlapping_chunks/overlapping_chunks_2.bin 9 | BINS += how2heap_poison_null_byte/poison_null_byte.bin how2heap_unsafe_unlink/unsafe_unlink.bin 10 | BINS += how2heap_unsorted_bin_attack/unsorted_bin_attack.bin how2heap_tcache_poisoning/tcache_poisoning.bin 11 | OBJECTS = $(BINS:.bin=.o) 12 | SOURCES = $(BINS:.bin=.c) 13 | RESULTS = $(BINS:.bin=.bin-result.yaml) 14 | DESCS = $(BINS:.bin=.bin-desc.yaml) 15 | POCS = ./*/pocs 16 | 17 | all: $(BINS) 18 | 19 | %.o: %.c 20 | $(CC) $(CFLAGS) -c -o $@ $^ 21 | 22 | %.bin: %.o 23 | $(CC) -L. -o $@ $^ $(LDFLAGS) -lc 24 | 25 | clean: 26 | rm -rf $(OBJECTS) $(RESULTS) $(DESCS) $(POCS) 27 | 28 | distclean: 29 | rm -rf $(BINS) $(OBJECTS) $(RESULTS) $(DESCS) $(POCS) 30 | -------------------------------------------------------------------------------- /heaphopper/analysis/mem_limiter.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import psutil 3 | import os 4 | import logging 5 | 6 | logger = logging.getLogger('MemLimiter') 7 | 8 | 9 | class MemLimiter(angr.exploration_techniques.ExplorationTechnique): 10 | def __init__(self, max_mem, drop_errored): 11 | super(MemLimiter, self).__init__() 12 | self.max_mem = max_mem 13 | self.drop_errored = drop_errored 14 | self.process = psutil.Process(os.getpid()) 15 | 16 | def step(self, simgr, stash='active', **kwargs): 17 | if psutil.virtual_memory().percent > 90 or (self.max_mem - 1) < self.memory_usage_psutil: 18 | simgr.move(from_stash='active', to_stash='out_of_memory') 19 | simgr.move(from_stash='deferred', to_stash='out_of_memory') 20 | 21 | simgr.drop(stash='deadended') 22 | simgr.drop(stash='avoid') 23 | simgr.drop(stash='found') 24 | if self.drop_errored: 25 | del simgr.errored[:] 26 | 27 | return simgr.step(stash=stash) 28 | 29 | @property 30 | def memory_usage_psutil(self): 31 | # return the memory usage in MB 32 | mem = self.process.memory_info().vms / float(2 ** 30) 33 | return mem 34 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_spirit/house_of_spirit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct __attribute__((__packed__)) { 9 | uint64_t * global_var; 10 | } controlled_data; 11 | 12 | typedef struct __attribute__((__packed__)) { 13 | uint64_t data[0x20]; 14 | } symbolic_data; 15 | 16 | void winning(void) { 17 | puts("You win!"); 18 | } 19 | 20 | size_t write_target[4]; 21 | size_t offset; 22 | size_t header_size; 23 | size_t mem2chunk_offset; 24 | size_t malloc_sizes[1]; 25 | size_t fill_sizes[1]; 26 | size_t overflow_sizes[0]; 27 | size_t arw_offsets[0]; 28 | size_t bf_offsets[0]; 29 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 30 | 31 | symbolic_data __attribute__((aligned(16))) sym_data; 32 | 33 | int main(void) { 34 | void *dummy_chunk = malloc(0x200); 35 | free(dummy_chunk); 36 | 37 | //sym_data_0.data[0x0] = 0x0; 38 | //sym_data.data[0x1] = 0x20; 39 | //sym_data.data[0x1] = malloc_sizes[0] + 0x10; 40 | //sym_data_0.data[0x9] = 0x1234; 41 | 42 | // VULN: Fake_free 43 | free(((uint8_t *) &sym_data.data) + mem2chunk_offset); 44 | 45 | // Allocation 46 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 47 | for (int i=0; i < fill_sizes[0]; i+=8) { 48 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 49 | } 50 | 51 | winning(); 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, The Regents of the University of California 2 | All rights reserved. 3 | 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /heaphopper/analysis/vuln_checker.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import logging 3 | from ..utils.input import check_input 4 | 5 | logger = logging.getLogger('VulnChecker') 6 | 7 | 8 | class VulnChecker(angr.exploration_techniques.ExplorationTechnique): 9 | def __init__(self, fd, pre_constraint, stdin_values, stop_found, filter_fake_free): 10 | super(VulnChecker, self).__init__() 11 | self.pre_constraint = pre_constraint 12 | self.stdin_values = stdin_values 13 | self.fd = fd 14 | self.stop_found = stop_found 15 | self.filter_fake_free = filter_fake_free 16 | 17 | def step(self, sm, stash, **kwargs): 18 | # We stop if we find the first vuln 19 | sm.move(from_stash='active', to_stash='vuln', filter_func=lambda p: p.heaphopper.vulnerable) 20 | 21 | if not self.pre_constraint and len(sm.vuln): 22 | sm.move(from_stash='vuln', to_stash='unsat_input', 23 | filter_func=lambda p: check_input(p, self.stdin_values, self.fd) is None) 24 | if not len(sm.vuln): 25 | logger.info('Vuln path not reachable through stdin constraints') 26 | 27 | if self.stop_found and len(sm.vuln): 28 | sm.move(from_stash='deferred', to_stash='unused') 29 | elif self.filter_fake_free and len(sm.vuln): 30 | if any(p.state.heaphopper.fake_frees for p in sm.vuln): 31 | sm.move(from_stash='deferred', to_stash='unused') 32 | 33 | return sm.step(stash=stash) 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Ask a question 2 | description: Ask a question about heaphopper 3 | labels: [question,needs-triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | If you have a question about heaphopper, that is not a bug report or a feature request, you can ask it here. For more real-time help with heaphopper, from us and the community, join our [Slack](https://angr.io/invite/). 9 | 10 | Before submitting this question, please check the following, which may answer your question: 11 | * Have you checked the [documentation](https://docs.angr.io/)? 12 | * Have you checked the [FAQ](https://docs.angr.io/introductory-errata/faq)? 13 | * Have you checked our library of [examples](https://github.com/angr/angr-doc/tree/master/examples)? 14 | * Have you [searched existing issues](https://github.com/angr/heaphopper/issues?q=is%3Aissue+label%3Aquestion) to see if this question has been answered before? 15 | * Have you checked that you are running the latest versions of angr and its components. angr is rapidly-evolving! 16 | 17 | **Please note: This repo is effectively unmaintained. While we appreciate bug reports and feature requests, we cannot commit to a timely response.** For more real-time help with angr, from us and the community, join our [Slack](https://angr.io/invite/). 18 | 19 | - type: textarea 20 | attributes: 21 | label: Question 22 | description: 23 | validations: 24 | required: true 25 | -------------------------------------------------------------------------------- /tests/how2heap_unsafe_unlink/unsafe_unlink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct __attribute__((__packed__)) { 9 | uint64_t * global_var; 10 | } controlled_data; 11 | 12 | typedef struct __attribute__((__packed__)) { 13 | uint64_t data[0x20]; 14 | } symbolic_data; 15 | 16 | void winning(void) { 17 | puts("You win!"); 18 | } 19 | 20 | size_t write_target[4]; 21 | size_t offset; 22 | size_t header_size; 23 | size_t mem2chunk_offset; 24 | size_t malloc_sizes[2]; 25 | size_t fill_sizes[2]; 26 | size_t overflow_sizes[1]; 27 | size_t arw_offsets[0]; 28 | size_t bf_offsets[0]; 29 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 31 | 32 | int main(void) { 33 | void *dummy_chunk = malloc(0x200); 34 | free(dummy_chunk); 35 | 36 | // Allocation 37 | //ctrl_data_0.global_var = malloc(0x80); 38 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 39 | for (int i=0; i < fill_sizes[0]; i+=8) { 40 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 41 | } 42 | 43 | // Allocation 44 | //ctrl_data_1.global_var = malloc(0x80); 45 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 46 | for (int i=0; i < fill_sizes[1]; i+=8) { 47 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 48 | } 49 | 50 | // VULN: Overflow 51 | offset = mem2chunk_offset; 52 | read(3, ((char *) ctrl_data_1.global_var)-offset, overflow_sizes[0]); 53 | 54 | free(ctrl_data_1.global_var); 55 | 56 | winning(); 57 | } 58 | 59 | 60 | -------------------------------------------------------------------------------- /tests/how2heap_tcache_poisoning/tcache_poisoning.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct __attribute__((__packed__)) { 8 | uint64_t * global_var; 9 | } controlled_data; 10 | 11 | typedef struct __attribute__((__packed__)) { 12 | uint64_t ctrl_0[0x40]; 13 | uint64_t * ptr; 14 | uint64_t ctrl_1[0x40]; 15 | } symbolic_data; 16 | 17 | void winning(void) { 18 | puts("You win!"); 19 | } 20 | 21 | size_t write_target[4]; 22 | size_t offset; 23 | size_t header_size; 24 | size_t mem2chunk_offset; 25 | size_t malloc_sizes[3]; 26 | size_t overflow_sizes[1]; 27 | size_t fill_sizes[3]; 28 | size_t arw_offsets[0]; 29 | size_t bf_offsets[0]; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 32 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 33 | 34 | int main(void) { 35 | void *dummy_chunk = malloc(0x200); 36 | free(dummy_chunk); 37 | 38 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 39 | for (int i=0; i < fill_sizes[0]; i+=8) { 40 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 41 | } 42 | 43 | 44 | free(ctrl_data_0.global_var); 45 | 46 | // VULN: UAF 47 | read(3, ctrl_data_0.global_var, header_size); 48 | 49 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 50 | for (int i=0; i < fill_sizes[1]; i+=8) { 51 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 52 | } 53 | 54 | ctrl_data_2.global_var = malloc(malloc_sizes[2]); 55 | for (int i=0; i < fill_sizes[2]; i+=8) { 56 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 57 | } 58 | 59 | winning(); 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_einherjar/house_of_einherjar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct __attribute__((__packed__)) { 9 | uint64_t * global_var; 10 | } controlled_data; 11 | 12 | typedef struct __attribute__((__packed__)) { 13 | uint64_t data[0x80]; 14 | } symbolic_data; 15 | 16 | void winning(void) { 17 | puts("You win!"); 18 | } 19 | 20 | size_t write_target[4]; 21 | size_t offset; 22 | size_t header_size; 23 | size_t mem2chunk_offset; 24 | size_t malloc_sizes[3]; 25 | size_t fill_sizes[3]; 26 | size_t overflow_sizes[1]; 27 | size_t arw_offsets[0]; 28 | size_t bf_offsets[0]; 29 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 32 | 33 | 34 | int main(void) { 35 | void *dummy_chunk = malloc(0x200); 36 | free(dummy_chunk); 37 | 38 | // Allocation 39 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 40 | for (int i=0; i < fill_sizes[0]; i+=8) { 41 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 42 | } 43 | 44 | // Allocation 45 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 46 | for (int i=0; i < fill_sizes[1]; i+=8) { 47 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 48 | } 49 | 50 | // VULN: Overflow 51 | offset = mem2chunk_offset; 52 | read(3, ((char *) ctrl_data_0.global_var)-offset, overflow_sizes[0]); 53 | 54 | free(ctrl_data_0.global_var); 55 | 56 | // Allocation 57 | ctrl_data_2.global_var = malloc(malloc_sizes[2]); 58 | for (int i=0; i < fill_sizes[2]; i+=8) { 59 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 60 | } 61 | 62 | winning(); 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Request a feature 2 | description: Request a new feature for heaphopper 3 | labels: [enhancement,needs-triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for taking the time to submit this feature request! 9 | 10 | Before submitting this feature request, please check the following: 11 | * Have you checked that you are running the latest versions of angr and its components? angr is rapidly-evolving! 12 | * Have you checked the [documentation](https://docs.angr.io/) to see if this feature exists already? 13 | * Have you [searched existing issues](https://github.com/angr/heaphopper/issues?q=is%3Aissue+label%3Aenhancement+) to see if this feature has been requested before? 14 | 15 | **Please note: This repo is effectively unmaintained. While we appreciate bug reports and feature requests, we cannot commit to a timely response.** For more real-time help with angr, from us and the community, join our [Slack](https://angr.io/invite/). 16 | 17 | - type: textarea 18 | attributes: 19 | label: Description 20 | description: | 21 | Brief description of the desired feature. If the feature is intended to solve some problem, please clearly describe the problem, including any relevant binaries, etc. 22 | 23 | **Tip:** You can attach files to the issue by first clicking on the textarea to select it, then dragging & dropping the file onto the textarea. 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | attributes: 29 | label: Alternatives 30 | description: Possible alternative solutions or features that you have considered. 31 | 32 | - type: textarea 33 | attributes: 34 | label: Additional context 35 | description: Any other context or screenshots about the feature request. 36 | -------------------------------------------------------------------------------- /tests/how2heap_fastbin_dup/fastbin_dup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct __attribute__((__packed__)) { 8 | uint64_t * global_var; 9 | } controlled_data; 10 | 11 | typedef struct __attribute__((__packed__)) { 12 | uint64_t ctrl_0[0x40]; 13 | uint64_t * ptr; 14 | uint64_t ctrl_1[0x40]; 15 | } symbolic_data; 16 | 17 | void winning(void) { 18 | puts("You win!"); 19 | } 20 | 21 | size_t write_target[4]; 22 | size_t offset; 23 | size_t header_size; 24 | size_t mem2chunk_offset; 25 | size_t malloc_sizes[4]; 26 | size_t overflow_sizes[1]; 27 | size_t fill_sizes[4]; 28 | size_t arw_offsets[0]; 29 | size_t bf_offsets[0]; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 32 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 33 | controlled_data __attribute__((aligned(16))) ctrl_data_3; 34 | 35 | int main(void) { 36 | void *dummy_chunk = malloc(0x200); 37 | free(dummy_chunk); 38 | 39 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 40 | for (int i=0; i < fill_sizes[0]; i+=8) { 41 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 42 | } 43 | 44 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 45 | for (int i=0; i < fill_sizes[1]; i+=8) { 46 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 47 | } 48 | 49 | free(ctrl_data_0.global_var); 50 | 51 | // VULN: UAF 52 | read(3, ctrl_data_0.global_var, header_size); 53 | 54 | ctrl_data_2.global_var = malloc(malloc_sizes[2]); 55 | for (int i=0; i < fill_sizes[2]; i+=8) { 56 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 57 | } 58 | 59 | ctrl_data_3.global_var = malloc(malloc_sizes[3]); 60 | for (int i=0; i < fill_sizes[3]; i+=8) { 61 | read(0, ((uint8_t *)ctrl_data_3.global_var)+i, 8); 62 | } 63 | 64 | winning(); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /tests/how2heap_unsorted_bin_attack/unsorted_bin_attack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct __attribute__((__packed__)) { 9 | uint64_t * global_var; 10 | } controlled_data; 11 | 12 | typedef struct __attribute__((__packed__)) { 13 | uint64_t data[0x20]; 14 | } symbolic_data; 15 | 16 | void winning(void) { 17 | puts("You win!"); 18 | } 19 | 20 | size_t write_target[4]; 21 | size_t offset; 22 | size_t header_size; 23 | size_t mem2chunk_offset; 24 | size_t malloc_sizes[3]; 25 | size_t fill_sizes[3]; 26 | size_t overflow_sizes[0]; 27 | size_t arw_offsets[0]; 28 | size_t bf_offsets[0]; 29 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 32 | 33 | int main(void){ 34 | void *dummy_chunk = malloc(0x200); 35 | free(dummy_chunk); 36 | 37 | // Allocation 38 | //ctrl_data_0.global_var = malloc(400); 39 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 40 | for (int i=0; i < fill_sizes[0]; i+=8) { 41 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 42 | } 43 | 44 | // Allocation 45 | //ctrl_data_1.global_var = malloc(400); 46 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 47 | for (int i=0; i < fill_sizes[1]; i+=8) { 48 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 49 | } 50 | 51 | free(ctrl_data_0.global_var); 52 | 53 | //------------VULNERABILITY----------- 54 | 55 | // VULN: UAF 56 | read(3, ctrl_data_0.global_var, header_size); 57 | //ctrl_data_0.global_var[1] = &write_target; 58 | 59 | // Allocation 60 | //ctrl_data_2.global_var = malloc(400); 61 | ctrl_data_1.global_var = malloc(malloc_sizes[2]); 62 | for (int i=0; i < fill_sizes[2]; i+=8) { 63 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 64 | } 65 | winning(); 66 | } 67 | -------------------------------------------------------------------------------- /tests/how2heap_fastbin_dup/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 8 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, uaf: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | 24 | 25 | ############################# 26 | # analysis settings # 27 | ############################# 28 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 29 | malloc_sizes: [0x8] 30 | # Specify the overflow sizes starting from prev_size 31 | overflow_sizes: [0] 32 | # Specify stdin should be pre-constrained 33 | input_pre_constraint: False 34 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 35 | input_values: any 36 | # Specify a chunk's header size (not including any user_data overlap) 37 | header_size: 8 38 | # Specify the offset between the user's memory and the start of the chunk 39 | mem2chunk_offset: 16 40 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 41 | chunk_fill_size: zero 42 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 43 | # heap-memory, freeing of fake chunks 44 | vulns: [bad_alloc] 45 | 46 | # Define where to store the proof-of-concept c-files 47 | pocs_path: pocs 48 | 49 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_einherjar/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 7 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, overflow: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | 24 | 25 | ############################# 26 | # analysis settings # 27 | ############################# 28 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 29 | malloc_sizes: [0x38, 0xf8, 0x200] 30 | # Specify the overflow sizes starting from prev_size 31 | overflow_sizes: [9] 32 | # Specify stdin should be pre-constrained 33 | input_pre_constraint: False 34 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 35 | input_values: any 36 | # Specify a chunk's header size (not including any user_data overlap) 37 | header_size: 16 38 | # Specify the offset between the user's memory and the start of the chunk 39 | mem2chunk_offset: 16 40 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 41 | chunk_fill_size: zero 42 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 43 | # heap-memory, freeing of fake chunks 44 | vulns: [bad_alloc] 45 | 46 | # Define where to store the proof-of-concept c-files 47 | pocs_path: pocs 48 | 49 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_spirit/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x100 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 4 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, fake_free: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | 24 | 25 | ############################# 26 | # analysis settings # 27 | ############################# 28 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 29 | malloc_sizes: [0x30] 30 | # Specify the overflow sizes starting from prev_size 31 | overflow_sizes: [0] 32 | # Specify stdin should be pre-constrained 33 | input_pre_constraint: False 34 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 35 | input_values: any 36 | # Specify a chunk's header size (not including any user_data overlap) 37 | header_size: 32 38 | # Specify the offset between the user's memory and the start of the chunk 39 | mem2chunk_offset: 16 40 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 41 | mem2size_offset: 8 42 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 43 | chunk_fill_size: zero 44 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 45 | # heap-memory, freeing of fake chunks 46 | vulns: [bad_alloc] 47 | 48 | # Define where to store the proof-of-concept c-files 49 | pocs_path: pocs 50 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_lore/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 9 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, uaf: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | 24 | 25 | ############################# 26 | # analysis settings # 27 | ############################# 28 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 29 | malloc_sizes: [100, 1000] 30 | # Specify the overflow sizes starting from prev_size 31 | overflow_sizes: [0] 32 | # Specify stdin should be pre-constrained 33 | input_pre_constraint: False 34 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 35 | input_values: any 36 | # Specify a chunk's header size (not including any user_data overlap) 37 | header_size: 32 38 | # Specify the offset between the user's memory and the start of the chunk 39 | mem2chunk_offset: 16 40 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 41 | mem2size_offset: 8 42 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 43 | chunk_fill_size: zero 44 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 45 | # heap-memory, freeing of fake chunks 46 | vulns: [bad_alloc] 47 | #store_desc: False 48 | 49 | # Define where to store the proof-of-concept c-files 50 | pocs_path: pocs 51 | -------------------------------------------------------------------------------- /tests/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | #global_config: ./analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # FD for memory corruptions: 10 | mem_corruption_fd: 3 11 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 12 | allocator: ./libc-2.23/libc.so.6 13 | libc: ./libc-2.23/libc.so.6 14 | loader: ./libc-2.23/ld-linux-x86-64.so.2 15 | 16 | 17 | ############################# 18 | # analysis settings # 19 | ############################# 20 | # Set log_level (DEBUG, INFO, ERROR) 21 | log_level: WARNING 22 | store_desc: True 23 | fix_loader_problem: False 24 | 25 | ############################# 26 | # memory performance # 27 | ############################# 28 | # Set Memory limiter (Makes sure you don't run out of memory. Highly recommended!) 29 | use_mem_limiter: True 30 | # Set the memory limit in GB 31 | mem_limit: 30 32 | # Set spiller (spill states to disk in order to save memory) 33 | spiller: False 34 | spiller_conf: {min: 1, max: 2, staging_min: 1, staging_max: 2} 35 | # Specify if states should be split on allocations sizes or "ored" instead: 36 | state_split_sizes: False 37 | # Drop errored states (e.g. SegFaults) 38 | drop_errored: True 39 | 40 | 41 | ############################# 42 | # solver performance # 43 | ############################# 44 | # Set DFS (depth-first explorations) 45 | use_dfs: True 46 | # Set VSA (value-set analysis) 47 | use_vsa: False 48 | # Set Veritesting (DSE+SSE) 49 | use_veritesting: False 50 | # Set Memory limiter (Makes sure you don't run out of memory. Highly recommended!) 51 | use_mem_limiter: True 52 | # Stop exploration as soon as a vuln state is found. Do not explore the whole space! 53 | stop_found: True 54 | # Stop found for fake_frees, exploration is pretty expensive on those 55 | filter_fake_frees: False 56 | # Set Concretizer (Tries to concretize symbolic values asap) 57 | use_concretizer: False 58 | 59 | ############################# 60 | # pocs-gen settings # 61 | ############################# 62 | # Results may contain symbolic values which allow for multiple solutions, specify how many solutions you want to get 63 | num_results: 1 64 | -------------------------------------------------------------------------------- /tests/how2heap_overlapping_chunks/overlapping_chunks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct __attribute__((__packed__)) { 9 | uint64_t * global_var; 10 | } controlled_data; 11 | 12 | typedef struct __attribute__((__packed__)) { 13 | uint64_t data[0x20]; 14 | } symbolic_data; 15 | 16 | void winning(void) { 17 | puts("You win!"); 18 | } 19 | 20 | size_t write_target[4]; 21 | size_t offset; 22 | size_t header_size; 23 | size_t mem2chunk_offset; 24 | size_t malloc_sizes[4]; 25 | size_t fill_sizes[4]; 26 | size_t overflow_sizes[1]; 27 | size_t arw_offsets[0]; 28 | size_t bf_offsets[0]; 29 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 32 | controlled_data __attribute__((aligned(16))) ctrl_data_3; 33 | 34 | int main(void){ 35 | void *dummy_chunk = malloc(0x200); 36 | free(dummy_chunk); 37 | 38 | // Allocation 39 | //ctrl_data_0.global_var = malloc(0x100-8); 40 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 41 | for (int i=0; i < fill_sizes[0]; i+=8) { 42 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 43 | } 44 | 45 | // Allocation 46 | //ctrl_data_1.global_var = malloc(0x100-8); 47 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 48 | for (int i=0; i < fill_sizes[1]; i+=8) { 49 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 50 | } 51 | 52 | // Allocation 53 | //ctrl_data_2.global_var = malloc(0x80-8); 54 | ctrl_data_2.global_var = malloc(malloc_sizes[2]); 55 | for (int i=0; i < fill_sizes[2]; i+=8) { 56 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 57 | } 58 | 59 | 60 | free(ctrl_data_1.global_var); 61 | 62 | 63 | 64 | // VULN: Overflow 65 | offset = mem2chunk_offset; 66 | read(3, ((char *) ctrl_data_1.global_var)-offset, overflow_sizes[0]); 67 | //ctrl_data_1.global_var[-1] = 0x181; 68 | 69 | // Allocation 70 | //ctrl_data_3.global_var = malloc(0x180-8); 71 | ctrl_data_3.global_var = malloc(malloc_sizes[3]); 72 | for (int i=0; i < fill_sizes[3]; i+=8) { 73 | read(0, ((uint8_t *)ctrl_data_3.global_var)+i, 8); 74 | } 75 | 76 | winning(); 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /tests/how2heap_tcache_poisoning/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 10 | allocator: ../libc-2.27/libc.so.6 11 | libc: ../libc-2.27/libc.so.6 12 | loader: ../libc-2.27/ld-linux-x86-64.so.2 13 | # Create files ? 14 | create_files: False 15 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 16 | wtarget_size: 4 17 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 18 | sym_data_size: 0x0 19 | # Specify the number of actions in one zoo case 20 | zoo_depth: 8 21 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 22 | zoo_actions: {malloc: -1, free: -1, uaf: 1} 23 | # Specify the location to store your zoo 24 | zoo_dir: /tmp/zoo_dir 25 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 26 | mcheck: disable 27 | 28 | 29 | ############################# 30 | # analysis settings # 31 | ############################# 32 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 33 | malloc_sizes: [0x8] 34 | # Specify the overflow sizes starting from prev_size 35 | overflow_sizes: [0] 36 | # Specify stdin should be pre-constrained 37 | input_pre_constraint: False 38 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 39 | input_values: any 40 | # Specify a chunk's header size (not including any user_data overlap) 41 | header_size: 8 42 | # Specify the offset between the user's memory and the start of the chunk 43 | mem2chunk_offset: 16 44 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 45 | chunk_fill_size: zero 46 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 47 | # heap-memory, freeing of fake chunks 48 | vulns: [bad_alloc] 49 | 50 | # Define where to store the proof-of-concept c-files 51 | pocs_path: pocs 52 | fix_loader_problem: True 53 | 54 | -------------------------------------------------------------------------------- /heaphopper/utils/input.py: -------------------------------------------------------------------------------- 1 | import claripy 2 | import sys 3 | import logging 4 | 5 | logger = logging.getLogger('WinConditionTracker') 6 | logger.setLevel(logging.ERROR) 7 | 8 | def check_input(state, values, fd): 9 | if values == 'printable': 10 | ranges = [['!', '~']] 11 | elif values == 'alphanumeric': 12 | ranges = [['0', '9'], ['A', 'Z'], ['a', 'z']] 13 | elif values == 'letters': 14 | ranges = [['A', 'Z'], ['a', 'z']] 15 | elif values == 'zero-bytes': 16 | ranges = [['\0', '\0']] 17 | elif values == 'ascii': 18 | ranges = [['\0', '\x7f']] 19 | elif values == 'any': 20 | return state 21 | else: 22 | logger.error('Invalid input constraint') 23 | sys.exit(-1) 24 | 25 | 26 | stdin = state.posix.get_fd(fd).all_bytes().chop(8) 27 | constraints = claripy.And() 28 | for c in stdin: 29 | constraint = claripy.And() 30 | for r in ranges: 31 | constraint = claripy.And(c >= r[0], c <= r[1], constraint) 32 | constraints = claripy.And(constraint, constraints) 33 | if state.solver.satisfiable(extra_constraints=[constraints]): 34 | state.add_constraints(constraints) 35 | return state 36 | else: 37 | return None 38 | 39 | def constrain_input(state, stdin, values): 40 | if values == 'printable': 41 | ranges = [['!', '~']] 42 | elif values == 'alphanumeric': 43 | ranges = [['0', '9'], ['A', 'Z'], ['a', 'z']] 44 | elif values == 'letters': 45 | ranges = [['A', 'Z'], ['a', 'z']] 46 | elif values == 'zero-bytes': 47 | ranges = [['\0', '\0']] 48 | elif values == 'ascii': 49 | ranges = [['\0', '\x7f']] 50 | elif values == 'any': 51 | return state 52 | else: 53 | logger.error('Invalid input constraint') 54 | sys.exit(-1) 55 | 56 | constraints = claripy.And() 57 | for c in stdin: 58 | constraint = claripy.And() 59 | for r in ranges: 60 | constraint = claripy.And(c >= r[0], c <= r[1], constraint) 61 | constraints = claripy.And(constraint, constraints) 62 | if state.solver.satisfiable(extra_constraints=[constraints]): 63 | state.add_constraints(constraints) 64 | return state 65 | else: 66 | return None 67 | -------------------------------------------------------------------------------- /tests/how2heap_unsorted_bin_attack/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 7 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, uaf: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 24 | 25 | 26 | ############################# 27 | # analysis settings # 28 | ############################# 29 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 30 | malloc_sizes: [400] 31 | # Specify the overflow sizes starting from chunk-mem2chunk_offset 32 | overflow_sizes: [0] 33 | # Specify stdin should be pre-constrained 34 | input_pre_constraint: False 35 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 36 | input_values: any 37 | # Specify a chunk's header size (not including any user_data overlap) 38 | header_size: 32 39 | # Specify the offset between the user's memory and the start of the chunk 40 | mem2chunk_offset: 16 41 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 42 | mem2size_offset: 8 43 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 44 | chunk_fill_size: zero 45 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 46 | # heap-memory, freeing of fake chunks 47 | vulns: [arb_write] 48 | 49 | # Define where to store the proof-of-concept c-files 50 | pocs_path: pocs 51 | -------------------------------------------------------------------------------- /tests/how2heap_unsafe_unlink/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 6 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, overflow: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 24 | 25 | 26 | ############################# 27 | # analysis settings # 28 | ############################# 29 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 30 | malloc_sizes: [0x80] 31 | # Specify the overflow sizes starting from chunk-mem2chunk_offset 32 | overflow_sizes: [9] 33 | # Specify stdin should be pre-constrained 34 | input_pre_constraint: False 35 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 36 | input_values: any 37 | # Specify a chunk's header size (not including any user_data overlap) 38 | header_size: 32 39 | # Specify the offset between the user's memory and the start of the chunk 40 | mem2chunk_offset: 16 41 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 42 | mem2size_offset: 8 43 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 44 | chunk_fill_size: header_size 45 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 46 | # heap-memory, freeing of fake chunks 47 | vulns: [arb_write] 48 | 49 | # Define where to store the proof-of-concept c-files 50 | pocs_path: pocs 51 | -------------------------------------------------------------------------------- /tests/how2heap_overlapping_chunks/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 8 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, overflow: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 24 | 25 | 26 | ############################# 27 | # analysis settings # 28 | ############################# 29 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 30 | malloc_sizes: [0x78, 0xf8, 0x178] 31 | # Specify the overflow sizes starting from chunk-mem2chunk_offset 32 | overflow_sizes: [9] 33 | # Specify stdin should be pre-constrained 34 | input_pre_constraint: True 35 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 36 | input_values: any 37 | # Specify a chunk's header size (not including any user_data overlap) 38 | header_size: 32 39 | # Specify the offset between the user's memory and the start of the chunk 40 | mem2chunk_offset: 16 41 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 42 | mem2size_offset: 8 43 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 44 | chunk_fill_size: zero 45 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 46 | # heap-memory, freeing of fake chunks 47 | vulns: [overlap_alloc] 48 | #store_desc: False 49 | 50 | 51 | # Define where to store the proof-of-concept c-files 52 | pocs_path: pocs/ 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Report a bug 2 | description: Report a bug in heaphopper 3 | labels: [bug,needs-triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for taking the time to submit this bug report! 9 | 10 | Before submitting this bug report, please check the following, which may resolve your issue: 11 | * Have you checked that you are running the latest versions of angr and its components? angr is rapidly-evolving! 12 | * Have you [searched existing issues](https://github.com/angr/heaphopper/issues?q=is%3Aopen+is%3Aissue+label%3Abug) to see if this bug has been reported before? 13 | * Have you checked the [documentation](https://docs.angr.io/)? 14 | * Have you checked the [FAQ](https://docs.angr.io/introductory-errata/faq)? 15 | 16 | **Important:** If this bug is a security vulnerability, please submit it privately. See our [security policy](https://github.com/angr/angr/blob/master/SECURITY.md) for more details. 17 | 18 | **Please note: This repo is effectively unmaintained. While we appreciate bug reports and feature requests, we cannot commit to a timely response.** For more real-time help with angr, from us and the community, join our [Slack](https://angr.io/invite/). 19 | 20 | - type: textarea 21 | attributes: 22 | label: Description 23 | description: Brief description of the bug, with any relevant log messages. 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | attributes: 29 | label: Steps to reproduce the bug 30 | description: | 31 | If appropriate, include both a **script to reproduce the bug**, and if possible **attach the binary used**. 32 | 33 | **Tip:** You can attach files to the issue by first clicking on the textarea to select it, then dragging & dropping the file onto the textarea. 34 | - type: textarea 35 | attributes: 36 | label: Environment 37 | description: Many common issues are caused by problems with the local Python environment. Before submitting, double-check that your versions of all modules in the angr suite (angr, cle, pyvex, ...) are up to date and include the output of `python -m angr.misc.bug_report` here. 38 | 39 | - type: textarea 40 | attributes: 41 | label: Additional context 42 | description: Any additional context about the problem. 43 | -------------------------------------------------------------------------------- /tests/how2heap_overlapping_chunks/analysis_2.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 10 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, overflow: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 24 | 25 | 26 | ############################# 27 | # analysis settings # 28 | ############################# 29 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 30 | malloc_sizes: [1000, 2000] 31 | # Specify the overflow sizes starting from chunk-mem2chunk_offset 32 | overflow_sizes: [16] 33 | # Specify stdin should be pre-constrained 34 | stdin_pre_constraint: True 35 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 36 | stdin_values: any 37 | # Specify a chunk's header size (not including any user_data overlap) 38 | header_size: 32 39 | # Specify the offset between the user's memory and the start of the chunk 40 | mem2chunk_offset: 16 41 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 42 | mem2size_offset: 8 43 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 44 | chunk_fill_size: zero 45 | # Set log_level (DEBUG, INFO, ERROR) 46 | log_level: DEBUG 47 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 48 | # heap-memory, freeing of fake chunks 49 | vulns: [overlap_alloc] 50 | 51 | 52 | # Define where to store the proof-of-concept c-files 53 | pocs_path: tests/how2heap_overlapping_chunks/pocs 54 | -------------------------------------------------------------------------------- /tests/how2heap_poison_null_byte/analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | global_config: ../analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files ? 10 | create_files: False 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x40) 14 | sym_data_size: 0x0 15 | # Specify the number of actions in one zoo case 16 | zoo_depth: 12 17 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 18 | zoo_actions: {malloc: -1, free: -1, overflow: 1} 19 | # Specify the location to store your zoo 20 | zoo_dir: /tmp/zoo_dir 21 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 22 | mcheck: disable 23 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 24 | 25 | 26 | ############################# 27 | # analysis settings # 28 | ############################# 29 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 30 | #malloc_sizes: [120, 472, 984] 31 | malloc_sizes: [0x80, 0x100, 0x200] 32 | #malloc_sizes: [0x80, 0x100, 0x200] 33 | #malloc_sizes: [0x78, 0x3d8] 34 | # Specify the overflow sizes starting from chunk-mem2chunk_offset 35 | #overflow_sizes: [8, 9, 17] 36 | overflow_sizes: [9] 37 | # Specify stdin should be pre-constrained 38 | input_pre_constraint: True 39 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 40 | input_values: zero-bytes 41 | # Specify a chunk's header size (not including any user_data overlap) 42 | header_size: 32 43 | # Specify the offset between the user's memory and the start of the chunk 44 | mem2chunk_offset: 16 45 | # Specify the offset between the user's memory and the chunk's size (only used to detect overlapping chunks right now) 46 | mem2size_offset: 8 47 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 48 | chunk_fill_size: zero 49 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 50 | # heap-memory, freeing of fake chunks 51 | vulns: [overlap_alloc] 52 | 53 | # Define where to store the proof-of-concept c-files 54 | pocs_path: pocs 55 | -------------------------------------------------------------------------------- /tests/how2heap_house_of_lore/house_of_lore.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct __attribute__((__packed__)) { 9 | uint64_t * global_var; 10 | } controlled_data; 11 | 12 | typedef struct __attribute__((__packed__)) { 13 | uint64_t data[0x0]; 14 | } symbolic_data; 15 | 16 | void winning(void) { 17 | puts("You win!"); 18 | } 19 | 20 | size_t write_target[4]; 21 | size_t offset; 22 | size_t header_size; 23 | size_t mem2chunk_offset; 24 | size_t malloc_sizes[5]; 25 | size_t fill_sizes[5]; 26 | size_t overflow_sizes[0]; 27 | size_t arw_offsets[0]; 28 | size_t bf_offsets[0]; 29 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 32 | controlled_data __attribute__((aligned(16))) ctrl_data_3; 33 | controlled_data __attribute__((aligned(16))) ctrl_data_4; 34 | 35 | symbolic_data __attribute__((aligned(16))) sym_data; 36 | 37 | int main(void){ 38 | void *dummy_chunk = malloc(0x200); 39 | free(dummy_chunk); 40 | 41 | // Allocation 42 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 43 | for (int i=0; i < fill_sizes[0]; i+=8) { 44 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 45 | } 46 | 47 | // Allocation 48 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 49 | for (int i=0; i < fill_sizes[1]; i+=8) { 50 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 51 | } 52 | 53 | free(ctrl_data_0.global_var); 54 | 55 | // Allocation 56 | ctrl_data_2.global_var = malloc(malloc_sizes[2]); 57 | for (int i=0; i < fill_sizes[2]; i+=8) { 58 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 59 | } 60 | 61 | //// VULN: UAF 62 | read(3, ctrl_data_0.global_var, header_size); 63 | //ctrl_data_0.global_var[1] = &sym_data_0.data; 64 | //sym_data_0.data[0] = 0; 65 | //sym_data_0.data[1] = 0; 66 | //sym_data_0.data[2] = ctrl_data_0.global_var-2; 67 | //sym_data_0.data[3] = &sym_data_1.data; 68 | //sym_data_1.data[2] = &sym_data_0.data; 69 | //sym_data_1.data[3] = 0; 70 | 71 | // Allocation 72 | ctrl_data_3.global_var = malloc(malloc_sizes[3]); 73 | for (int i=0; i < fill_sizes[3]; i+=8) { 74 | read(0, ((uint8_t *)ctrl_data_3.global_var)+i, 8); 75 | } 76 | 77 | //Allocation 78 | ctrl_data_4.global_var = malloc(malloc_sizes[4]); 79 | for (int i=0; i < fill_sizes[4]; i+=8) { 80 | read(0, ((uint8_t *)ctrl_data_4.global_var)+i, 8); 81 | } 82 | 83 | winning(); 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | heaphopper 2 | === 3 | 4 | [![Build Status](https://travis-ci.org/angr/heaphopper.svg?branch=master)](https://travis-ci.org/angr/heaphopper) 5 | [![License](https://img.shields.io/github/license/angr/angr.svg)](https://github.com/angr/heaphopper/blob/master/LICENSE) 6 | 7 | HeapHopper is a bounded model checking framework for Heap-implementations. 8 | 9 | # Overview 10 | 11 | ![Overview](overview.png) 12 | 13 | # Setup 14 | 15 | ``` bash 16 | sudo apt update && sudo apt install build-essential python3-dev virtualenvwrapper 17 | git clone https://github.com/angr/heaphopper.git && cd ./heaphopper 18 | mkvirtualenv -ppython3 heaphopper 19 | pip install -e . 20 | ``` 21 | 22 | #### Required Packages 23 | ``` bash 24 | build-essential python3-dev virtualenvwrapper 25 | ``` 26 | 27 | #### Required Python-Packages 28 | ``` bash 29 | ana angr cle claripy psutil pyelftools pyyaml 30 | ``` 31 | 32 | # Examples 33 | 34 | ``` bash 35 | # Gen zoo of permutations 36 | ./heaphopper_client.py gen -c analysis.yaml 37 | 38 | # Trace instance 39 | make -C tests 40 | ./heaphopper_client.py trace -c tests/how2heap_fastbin_dup/analysis.yaml -b tests/how2heap_fastbin_dup/fastbin_dup.bin 41 | 42 | # Gen PoC 43 | ./heaphopper_client.py poc -c tests/how2heap_fastbin_dup/analysis.yaml -r tests/how2heap_fastbin_dup/fastbin_dup.bin-result.yaml -d tests/how2heap_fastbin_dup/fastbin_dup.bin-desc.yaml -s tests/how2heap_fastbin_dup/fastbin_dup.c -b tests/how2heap_fastbin_dup/fastbin_dup.bin 44 | 45 | # Tests 46 | ## Show source 47 | cat tests/how2heap_fastbin_dup/fastbin_dup.c 48 | ## Run tests 49 | tests/test_heaphopper.py 50 | ## Show PoC source 51 | cat tests/how2heap_fastbin_dup/pocs/malloc_non_heap/fastbin_dup.bin/poc_0_0.c 52 | ## Run PoC 53 | cd tests 54 | ./run_poc.sh tests/how2heap_fastbin_dup/pocs/malloc_non_heap/fastbin_dup.bin/bin/poc_0_0.bin 55 | ``` 56 | 57 | # Publication 58 | This work has been published at the [27th USENIX Security Symposium](https://www.usenix.org/conference/usenixsecurity18/presentation/eckert). 59 | 60 | You can read the paper [here](https://seclab.cs.ucsb.edu/media/uploads/papers/sec2018-heap-hopper.pdf). 61 | 62 | Cite: 63 | ``` 64 | @inproceedings {heaphopper, 65 | author = {Eckert, Moritz and Bianchi, Antonio and Wang, Ruoyu and Shoshitaishvili, Yan and Kruegel, Christopher and Vigna, Giovanni}, 66 | title = {HeapHopper: Bringing Bounded Model Checking to Heap Implementation Security}, 67 | booktitle = {27th {USENIX} Security Symposium ({USENIX} Security 18)}, 68 | year = {2018}, 69 | address = {Baltimore, MD}, 70 | url = {https://www.usenix.org/conference/usenixsecurity18/presentation/eckert}, 71 | publisher = {{USENIX} Association}, 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /heaphopper_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import argparse 5 | from heaphopper.analysis.tracer.tracer import trace 6 | from heaphopper.analysis.identify_bins.identifier import identify 7 | from heaphopper.gen.gen_zoo import gen_zoo 8 | from heaphopper.gen.gen_pocs import gen_pocs 9 | 10 | 11 | def run_identifier(config): 12 | ret = identify(config) 13 | sys.exit(ret) 14 | 15 | 16 | def run_tracer(config, binary): 17 | ret = trace(config, binary) 18 | sys.exit(ret) 19 | 20 | 21 | def run_zoo_gen(config): 22 | ret = gen_zoo(config) 23 | sys.exit(ret) 24 | 25 | def run_poc_gen(config, binary, result, desc, source): 26 | gen_pocs(config, binary, result, desc, source) 27 | sys.exit(0) 28 | 29 | 30 | if __name__ == '__main__': 31 | parser = argparse.ArgumentParser(description='Find heap corruptions') 32 | parser.add_argument('action', metavar='', type=str, 33 | choices=['identify', 'trace', 'gen', 'poc'], 34 | help='Identify bins or trace for vulns') 35 | parser.add_argument('-c', '--config', metavar='analysis.yaml', type=open, 36 | help='Path to config file in yaml-format') 37 | parser.add_argument('-b', '--binary', metavar='binary_path', type=str, 38 | help='Path to the binary to be traced') 39 | parser.add_argument('-r', '--result', metavar='result.yaml', type=str, 40 | help='Path to the result.yaml file for poc gen') 41 | parser.add_argument('-d', '--desc', metavar='desc.yaml', type=str, 42 | help='Path to the desc.yaml file for poc gen') 43 | parser.add_argument('-s', '--source', metavar='source.c', type=str, 44 | help='Path to the source file for poc gen') 45 | 46 | args = parser.parse_args() 47 | 48 | if args.action == 'trace': 49 | if args.config is None or args.binary is None: 50 | parser.error('trace requires --config and --binary') 51 | run_tracer(args.config, args.binary) 52 | elif args.action == 'gen': 53 | if args.config is None : 54 | parser.error('gen requires --config') 55 | run_zoo_gen(args.config) 56 | elif args.action == 'poc': 57 | if args.config is None or args.binary is None or args.result is None or args.desc is None or args.source is None: 58 | parser.error('poc requires --config, --binary, --result, --desc and --source') 59 | run_poc_gen(args.config, args.binary, args.result, args.desc, args.source) 60 | else: 61 | if args.config is None: 62 | parser.error('identify requires --config') 63 | run_identifier(args.config) 64 | -------------------------------------------------------------------------------- /tests/how2heap_overlapping_chunks/overlapping_chunks_2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct __attribute__((__packed__)) { 10 | uint64_t * global_var; 11 | } controlled_data; 12 | 13 | typedef struct __attribute__((__packed__)) { 14 | uint64_t data[0x20]; 15 | } symbolic_data; 16 | 17 | void winning(void) { 18 | puts("You win!"); 19 | } 20 | 21 | size_t write_target[4]; 22 | size_t offset; 23 | size_t header_size; 24 | size_t mem2chunk_offset; 25 | size_t malloc_sizes[5]; 26 | size_t fill_sizes[5]; 27 | size_t overflow_sizes[1]; 28 | size_t arw_offsets[0]; 29 | size_t bf_offsets[0]; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 32 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 33 | controlled_data __attribute__((aligned(16))) ctrl_data_3; 34 | controlled_data __attribute__((aligned(16))) ctrl_data_4; 35 | 36 | 37 | 38 | int main(void) { 39 | void *dummy_chunk = malloc(0x200); 40 | //free(dummy_chunk); 41 | 42 | // Allocation 43 | //ctrl_data_0.global_var = malloc(malloc_sizes[0]); 44 | ctrl_data_0.global_var = malloc(1000); 45 | for (int i=0; i < fill_sizes[0]; i+=8) { 46 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 47 | } 48 | 49 | // Allocation 50 | //ctrl_data_1.global_var = malloc(malloc_sizes[1]); 51 | ctrl_data_1.global_var = malloc(1000); 52 | for (int i=0; i < fill_sizes[1]; i+=8) { 53 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 54 | } 55 | 56 | // Allocation 57 | //ctrl_data_2.global_var = malloc(malloc_sizes[2]); 58 | ctrl_data_2.global_var = malloc(1000); 59 | for (int i=0; i < fill_sizes[2]; i+=8) { 60 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 61 | } 62 | 63 | // Allocation 64 | //ctrl_data_3.global_var = malloc(malloc_sizes[3]); 65 | ctrl_data_3.global_var = malloc(1000); 66 | for (int i=0; i < fill_sizes[3]; i+=8) { 67 | read(0, ((uint8_t *)ctrl_data_3.global_var)+i, 8); 68 | } 69 | 70 | free(ctrl_data_2.global_var); 71 | 72 | //*(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE 73 | // VULN: Overflow 74 | offset = mem2chunk_offset; 75 | read(0, ((char *) ctrl_data_0.global_var)-offset, overflow_sizes[0]); 76 | //read(0, ((char *) ctrl_data_0.global_var)-8, 2); 77 | //ctrl_data_0.global_var[-1] = 2017; 78 | 79 | free(ctrl_data_0.global_var); 80 | 81 | // Allocation 82 | // ctrl_data_4.global_var = malloc(malloc_sizes[4]); 83 | ctrl_data_4.global_var = malloc(2000); 84 | for (int i=0; i < fill_sizes[4]; i+=8) { 85 | read(0, ((uint8_t *)ctrl_data_4.global_var)+i, 8); 86 | } 87 | 88 | winning(); 89 | } 90 | -------------------------------------------------------------------------------- /tests/how2heap_poison_null_byte/poison_null_byte.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct __attribute__((__packed__)) { 9 | uint64_t * global_var; 10 | } controlled_data; 11 | 12 | typedef struct __attribute__((__packed__)) { 13 | uint64_t data[0x20]; 14 | } symbolic_data; 15 | 16 | void winning(void) { 17 | puts("You win!"); 18 | } 19 | 20 | size_t write_target[4]; 21 | size_t offset; 22 | size_t header_size; 23 | size_t mem2chunk_offset; 24 | size_t malloc_sizes[6]; 25 | size_t fill_sizes[6]; 26 | size_t overflow_sizes[1]; 27 | size_t arw_offsets[0]; 28 | size_t bf_offsets[0]; 29 | controlled_data __attribute__((aligned(16))) ctrl_data_0; 30 | controlled_data __attribute__((aligned(16))) ctrl_data_1; 31 | controlled_data __attribute__((aligned(16))) ctrl_data_2; 32 | controlled_data __attribute__((aligned(16))) ctrl_data_3; 33 | controlled_data __attribute__((aligned(16))) ctrl_data_4; 34 | controlled_data __attribute__((aligned(16))) ctrl_data_5; 35 | 36 | int main(void) { 37 | void *dummy_chunk = malloc(0x200); 38 | free(dummy_chunk); 39 | 40 | // Allocation 41 | //ctrl_data_0.global_var = malloc(0x100); 42 | ctrl_data_0.global_var = malloc(malloc_sizes[0]); 43 | for (int i=0; i < fill_sizes[0]; i+=8) { 44 | read(0, ((uint8_t *)ctrl_data_0.global_var)+i, 8); 45 | } 46 | 47 | // Allocation 48 | //ctrl_data_1.global_var = malloc(0x200); 49 | ctrl_data_1.global_var = malloc(malloc_sizes[1]); 50 | for (int i=0; i < fill_sizes[1]; i+=8) { 51 | read(0, ((uint8_t *)ctrl_data_1.global_var)+i, 8); 52 | } 53 | 54 | // Allocation 55 | //ctrl_data_2.global_var = malloc(0x100); 56 | ctrl_data_2.global_var = malloc(malloc_sizes[2]); 57 | for (int i=0; i < fill_sizes[2]; i+=8) { 58 | read(0, ((uint8_t *)ctrl_data_2.global_var)+i, 8); 59 | } 60 | 61 | free(ctrl_data_1.global_var); 62 | 63 | // VULN: Overflow 64 | offset = mem2chunk_offset; 65 | read(3, ((char *) ctrl_data_1.global_var)-offset, overflow_sizes[0]); 66 | //ctrl_data_1.global_var[-1] &= ~0xff; 67 | 68 | // Allocation 69 | //ctrl_data_3.global_var = malloc(0x100); 70 | ctrl_data_3.global_var = malloc(malloc_sizes[3]); 71 | for (int i=0; i < fill_sizes[3]; i+=8) { 72 | read(0, ((uint8_t *)ctrl_data_3.global_var)+i, 8); 73 | } 74 | 75 | // Allocation 76 | //ctrl_data_4.global_var = malloc(0x80); 77 | ctrl_data_4.global_var = malloc(malloc_sizes[4]); 78 | for (int i=0; i < fill_sizes[4]; i+=8) { 79 | read(0, ((uint8_t *)ctrl_data_4.global_var)+i, 8); 80 | } 81 | 82 | free(ctrl_data_3.global_var); 83 | free(ctrl_data_2.global_var); 84 | 85 | // Allocation 86 | ctrl_data_5.global_var = malloc(malloc_sizes[5]); 87 | //ctrl_data_5.global_var = malloc(0x200); 88 | for (int i=0; i < fill_sizes[5]; i+=8) { 89 | read(0, ((uint8_t *)ctrl_data_5.global_var)+i, 8); 90 | } 91 | 92 | winning(); 93 | } 94 | -------------------------------------------------------------------------------- /analysis.yaml: -------------------------------------------------------------------------------- 1 | # Heap analysis configuration file 2 | 3 | # Use global config as base 4 | #global_config: ./analysis.yaml 5 | 6 | ############################# 7 | # zoo-gen settings # 8 | ############################# 9 | # Create files or just show the amount of permutations 10 | create_files: True 11 | # Specify the write_target's size in qwords (controlled memory where you want to write to for arb. write) 12 | wtarget_size: 4 13 | # Symbolic bytes in sim_data used for fake_free, etc... (default: 0x20) 14 | sym_data_size: 0x20 15 | # FD for memory corruptions: 16 | mem_corruption_fd: 3 17 | # Specify the number of actions in one zoo case 18 | zoo_depth: 7 19 | # Specify the zoo actions out of (malloc, free, overflow, uaf, fake_free, double_free, single_bitflip, arb_relative_write) 20 | # Syntax: action:count (count sets the max. occurrences of the action per binary, -1 means zoo_depth) 21 | zoo_actions: {malloc: -1, free: -1, overflow: -1, fake_free: -1, double_free: -1, arb_relative_write: -1, single_bitflip: -1} 22 | # Specify the location to store your zoo 23 | zoo_dir: /tmp/zoo_dir 24 | # Enable additional malloc hardening for glibc (disable, enable, pedantic) 25 | mcheck: disable 26 | # Path to shared lib implementing the c-allocator API ("default" for using the libc) 27 | allocator: ./libc.so.6 28 | libc: ./libc.so.6 29 | loader: ld-linux-x86-64.so.2 30 | 31 | 32 | ############################# 33 | # analysis settings # 34 | ############################# 35 | # Specify the distinct malloc sizes; run identify_libc to identify the libc-binsizes 36 | malloc_sizes: [20, 200, 2000] 37 | # Specify the overflow sizes starting from prev_size 38 | overflow_sizes: [9, 12] 39 | # Specify stdin should be pre-constrained 40 | input_pre_constraint: False 41 | # Specify stdin value-range (any, ascii, printable, alphanumeric, letters, zero-bytes) 42 | input_values: any 43 | # Specify a chunk's header size 44 | header_size: 32 45 | # Specify the offset between the user's memory and the start of the chunk 46 | mem2chunk_offset: 16 47 | # Chunk fill size (number of byte read into each chunk when allocated): zero, header_size, chunk_size, 48 | chunk_fill_size: zero 49 | # Set log_level (DEBUG, INFO, ERROR) 50 | log_level: INFO 51 | # Specify vulnerabilities to detect: arbitrary write, allocations over already allocated memory, allocations over non- 52 | # heap-memory, freeing of fake chunks 53 | vulns: [arb_write, overlap_alloc, bad_alloc] 54 | store_desc: True 55 | fix_loader_problem: False 56 | 57 | ############################# 58 | # memory performance # 59 | ############################# 60 | # Set Memory limiter (Makes sure you don't run out of memory. Highly recommended!) 61 | use_mem_limiter: True 62 | # Set the memory limit in GB 63 | mem_limit: 30 64 | # Set spiller (spill states to disk in order to save memory) 65 | spiller: False 66 | spiller_conf: {min: 1, max: 2, staging_min: 1, staging_max: 2} 67 | # Specify if states should be split on allocations sizes or "ored" instead: 68 | state_split_sizes: False 69 | # Drop errored states (e.g. SegFaults) 70 | drop_errored: True 71 | 72 | 73 | ############################# 74 | # solver performance # 75 | ############################# 76 | # Set DFS (depth-first explorations) 77 | use_dfs: True 78 | # Set VSA (value-set analysis) 79 | use_vsa: False 80 | # Set Veritesting (DSE+SSE) 81 | use_veritesting: False 82 | # Stop exploration as soon as a vuln state is found. Do not explore the whole space! 83 | stop_found: False 84 | # Stop found for fake_frees, exploration is pretty expensive on those 85 | filter_fake_frees: True 86 | # Set Concretizer (Tries to concretize symbolic values asap) 87 | use_concretizer: False 88 | 89 | ############################# 90 | # pocs-gen settings # 91 | ############################# 92 | # Define where to store the proof-of-concept c-files 93 | pocs_path: ./pocs 94 | # Results may contain symbolic values which allow for multiple solutions, specify how many solutions you want to get 95 | num_results: 1 96 | -------------------------------------------------------------------------------- /heaphopper/analysis/identify_bins/identifier.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import logging 3 | import hashlib 4 | import re 5 | import subprocess 6 | import os 7 | 8 | from ..heap_condition_tracker import HeapConditionTracker 9 | from ..mem_limiter import MemLimiter 10 | from ...utils.angr_tools import heardEnter 11 | from ...utils.parse_config import parse_config 12 | 13 | logger = logging.getLogger('bin-identifier') 14 | 15 | 16 | def use_sim_procedure(name): 17 | if name in ['puts', 'printf']: 18 | return False 19 | else: 20 | return True 21 | 22 | 23 | def identify(config_file): 24 | config = parse_config(config_file) 25 | logger.setLevel(config['log_level']) 26 | logger.info('Identifying libc...') 27 | 28 | libc_path = os.path.expanduser(config['libc']) 29 | libc_name = os.path.basename(libc_path) 30 | libc_hash = hashlib.md5(open(libc_name, 'rb').read()).hexdigest() 31 | logger.debug('md5(libc) == {}'.format(libc_hash)) 32 | 33 | bins_file = '{}.bin_sizes'.format(libc_hash) 34 | if not os.path.isfile(bins_file): 35 | bins = identify_bins(config) 36 | with open(bins_file, 'w') as f: 37 | for cbin in bins: 38 | f.write('{} - {}\n'.format(cbin[0], cbin[1])) 39 | else: 40 | bins = [] 41 | with open(bins_file, 'r') as f: 42 | for line in f.read().split("\n"): 43 | if not line: 44 | continue 45 | start, end = re.findall(r'(\d*) - (\d*)', line)[0] 46 | bins.append((int(start, 10), int(end, 10))) 47 | 48 | logger.info('Found {} bins'.format(len(bins))) 49 | return bins 50 | 51 | 52 | def identify_bins(config): 53 | logger.info('Identifying bins...') 54 | # build identify bin 55 | subprocess.Popen(["make", "-C", "./heaphopper/analysis/identify_bins"], stdout=subprocess.PIPE) 56 | 57 | libc_path = config['libc'] 58 | 59 | # Create project and disable sim_procedures for the libc 60 | proj = angr.Project('./heaphopper/analysis/identify_bins/identify', 61 | auto_load_libs=True, 62 | exclude_sim_procedures_func=use_sim_procedure, 63 | custom_ld_path=[os.path.dirname(libc_path)]) 64 | 65 | # Create state and enable reverse memory map 66 | added_options = set() 67 | added_options.add(angr.options.REVERSE_MEMORY_NAME_MAP) 68 | added_options.add(angr.options.TRACK_MEMORY_ACTIONS) 69 | added_options.add(angr.options.CONSTRAINT_TRACKING_IN_SOLVER) 70 | added_options.add(angr.options.STRICT_PAGE_ACCESS) 71 | state = proj.factory.full_init_state(add_options=added_options, remove_options=angr.options.simplification) 72 | state.register_plugin('heap', HeapConditionTracker()) 73 | # fix_loader_problem(proj, state) 74 | 75 | malloc_sizes = proj.loader.main_object.get_symbol('malloc_sizes') 76 | 77 | states = [] 78 | for i in range(8, 0x1008, 8): 79 | s = state.copy() 80 | malloc_size = state.solver.BVV(i, 8 * 8) 81 | s.memory.store(malloc_sizes.rebased_addr, malloc_size, 8, endness='Iend_LE') 82 | 83 | states.append(s) 84 | 85 | sm = proj.factory.simgr(thing=states, immutable=False) 86 | sm.use_technique(MemLimiter(config['mem_limit'], config['drop_errored'])) 87 | 88 | debug = False 89 | stop = False 90 | while len(sm.active) > 0 and not stop: 91 | if debug: 92 | debug = False 93 | 94 | sm.step() 95 | print(sm) 96 | 97 | if heardEnter(): 98 | debug = True 99 | 100 | unique_paths = dict() 101 | for found in sm.deadended: 102 | trace = tuple(found.rebased_addr_trace.hardcopy) 103 | curr = found.state.solver.any_int(found.state.memory.load(malloc_sizes.rebased_addr, 8, endness='Iend_LE')) 104 | if trace in list(unique_paths.keys()): 105 | min_size, max_size = unique_paths[trace] 106 | if curr < min_size: 107 | min_size = curr 108 | if curr > max_size: 109 | max_size = curr 110 | unique_paths[trace] = (min_size, max_size) 111 | else: 112 | unique_paths[trace] = (curr, curr) 113 | 114 | sorted_bins = sorted(list(unique_paths.values()), key=lambda tup: tup[0]) 115 | for i, curr_bin in enumerate(sorted_bins): 116 | logger.debug('Bin[{}]: min={} max={}'.format(i, hex(curr_bin[0]), hex(curr_bin[1]))) 117 | return sorted_bins 118 | -------------------------------------------------------------------------------- /tests/test_heaphopper.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import glob 3 | import logging 4 | import os 5 | import re 6 | from subprocess import check_output, STDOUT, CalledProcessError 7 | import sys 8 | import time 9 | import unittest 10 | 11 | from flaky import flaky 12 | 13 | from heaphopper.analysis.tracer.tracer import trace 14 | from heaphopper.gen.gen_pocs import gen_pocs 15 | from heaphopper.utils.parse_config import parse_config 16 | 17 | logger = logging.getLogger("heaphopper.test") 18 | 19 | BASE_DIR = os.path.dirname(os.path.realpath(__file__)) 20 | 21 | 22 | def store_results(results_dict): 23 | ts = time.time() 24 | dt = datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d_%H:%M:%S") 25 | fn = "{}_test.txt".format(dt) 26 | 27 | total_time = 0 28 | with open(fn, "w") as f: 29 | f.write("Timing results for test run from {}\n\n".format(dt)) 30 | for d in results_dict.keys(): 31 | f.write( 32 | "[{}]{}: {} s\n".format( 33 | "OK" if results_dict[d]["worked"] else "FAILED", 34 | d, 35 | results_dict[d]["ts"], 36 | ) 37 | ) 38 | total_time += results_dict[d]["ts"] 39 | f.write("total time: {} s\n".format(total_time)) 40 | 41 | 42 | def run_single(config_path, binary_path): 43 | start = time.time() 44 | with open(config_path, "r") as config: 45 | trace(config, binary_path) 46 | ts = time.time() - start 47 | return ts 48 | 49 | 50 | def check_single(result_path, binary_path, config_path): 51 | ts = run_single(config_path, binary_path) 52 | if not os.path.isfile(result_path): 53 | logger.error("Error tracing %s. Log-output:", config_path) 54 | msg = ( 55 | "Couldn't find result files: This indicates a problem with the sybmolic execution in angr and means we " 56 | "failed to reach expected bad-state. " 57 | ) 58 | assert False, msg 59 | return ts 60 | 61 | def create_poc_single( 62 | folder_name, 63 | analysis_name, 64 | binary_name, 65 | result_name, 66 | desc_name, 67 | source_name, 68 | poc_path, 69 | ): 70 | config_path = os.path.join(folder_name, analysis_name) 71 | binary_path = os.path.join(folder_name, binary_name) 72 | 73 | with open(config_path, "r") as config: 74 | gen_pocs(config, binary_path, result_name, desc_name, source_name) 75 | 76 | poc_path = glob.glob(poc_path)[0] 77 | try: 78 | cmd = ["make", "-C", poc_path, "pocs-print"] 79 | check_output(cmd, stderr=STDOUT) 80 | except CalledProcessError as e: 81 | if e.output: 82 | logger.error("CalledProcessError: Traceback of running %s:", cmd) 83 | logger.error(e.output.decode("utf-8")) 84 | msg = ( 85 | "Failed to compile the synthesized concrete source code.\nMost likely the poc-generation created invalid " 86 | "C. This is a strong indication for ab bug in the poc-generation and most likely has nothing to do with " 87 | "the symbolic execution in angr. " 88 | ) 89 | assert False, msg 90 | return True 91 | 92 | 93 | def verify_poc_single(poc_path, poc_type, conf_path): 94 | try: 95 | f = open(conf_path, "r") 96 | config = parse_config(f) 97 | except OSError as err: 98 | logger.error("OS error: %s", err) 99 | return False 100 | 101 | libc_path = config["libc"] 102 | loader_path = config["loader"] 103 | 104 | poc_path = glob.glob(poc_path)[0] 105 | poc_bin = os.path.join(poc_path, "bin", "poc_0_0.bin") 106 | 107 | try: 108 | cmd = [loader_path, poc_bin] 109 | output = check_output( 110 | cmd, 111 | env={"LD_PRELOAD": libc_path, "LIBC_FATAL_STDERR_": "1"}, 112 | cwd=BASE_DIR, 113 | stderr=STDOUT, 114 | ) 115 | except CalledProcessError as e: 116 | logger.error("CalledProcessError: Traceback of running %s:", cmd) 117 | logger.error(e.output.decode("utf-8")) 118 | msg = ( 119 | "Running the POC failed with an non-zero exit code. This is a strong indication for a bug in the " 120 | "poc-generation and most likely has nothing to do with the symbolic execution in angr. " 121 | ) 122 | assert False, msg 123 | 124 | if poc_type == "malloc_non_heap": 125 | res = verify_non_heap(output) 126 | if not res: 127 | logger.error("Error running POC %s. output:", poc_bin) 128 | logger.error(output.decode("utf-8")) 129 | msg = ( 130 | "The concrete execution did not reach the malloc_non_heap state. This is a strong indication for a bug " 131 | "in the poc-generation and most likely has nothing to do with the symbolic execution in angr. " 132 | ) 133 | assert False, msg 134 | 135 | elif poc_type == "malloc_allocated": 136 | res = verify_malloc_allocated(output) 137 | if not res: 138 | logger.error("Error running POC %s. output:", poc_bin) 139 | logger.error(output.decode("utf-8")) 140 | msg = ( 141 | "The concrete execution did not reach the malloc_allocated state. This is a strong indication for a bug " 142 | "in the poc-generation and most likely has nothing to do with the symbolic execution in angr. " 143 | ) 144 | assert False, msg 145 | 146 | elif poc_type.startswith("arbitrary_write"): 147 | res = verify_arbitrary_write(output) 148 | if not res: 149 | logger.error("Error running POC %s. output:", poc_bin) 150 | logger.error(output.decode("utf-8")) 151 | msg = ( 152 | "The concrete execution did not trigger an arbitrary write. This is a strong indication for a bug in " 153 | "the poc-generation and most likely has nothing to do with the symbolic execution in angr. " 154 | ) 155 | assert False, msg 156 | else: 157 | res = True 158 | 159 | return res 160 | 161 | 162 | def verify_non_heap(output): 163 | heap_base = int(re.findall(b"Init printf: ([0-9a-fx]+)", output)[0], 0) 164 | last_alloc = int(re.findall(b"Allocation: ([0-9a-fx]+)", output)[-1], 0) 165 | if last_alloc < heap_base: 166 | return True 167 | return False 168 | 169 | 170 | def verify_malloc_allocated(output): 171 | allocs = [ 172 | (int(f[0], 16), int(f[1], 16)) 173 | for f in re.findall(b"Allocation: ([0-9a-fx]+)\nSize: ([0-9a-fx]+)", output) 174 | ] 175 | for i, (a1, s1) in enumerate(allocs): 176 | for a2, s2 in allocs[i + 1 :]: 177 | if a1 == a2: 178 | return True 179 | if a1 < a2 < a1 + s1: 180 | return True 181 | if a2 < a1 < a2 + s2: 182 | return True 183 | 184 | return False 185 | 186 | 187 | def verify_arbitrary_write(output): 188 | pre = dict() 189 | for (i, a) in re.findall( 190 | br"write_target\[([0-9]+)\]: ([0-9a-fx]+|\(nil\))\n", output 191 | ): 192 | if a == b"(nil)": 193 | a = "0x0" 194 | 195 | if i not in pre: 196 | pre[i] = int(a, 0) 197 | else: 198 | if pre[i] != int(a, 0): 199 | return True 200 | 201 | return False 202 | 203 | 204 | @unittest.skip("Broken on Ubuntu 22.04") 205 | class TestHeapHopper(unittest.TestCase): 206 | def setUp(self): 207 | output = check_output(["make", "-C", BASE_DIR, "clean"]) 208 | logger.debug(output) 209 | output = check_output(["make", "-C", BASE_DIR]) 210 | logger.debug(output) 211 | 212 | def do_test(self, name, type_, poc_star=False): 213 | bin_name = name + ".bin" 214 | conf = "analysis.yaml" 215 | 216 | location = os.path.join(BASE_DIR, "how2heap_" + name) 217 | result_path = os.path.join(location, bin_name + "-result.yaml") 218 | desc_path = os.path.join(location, bin_name + "-desc.yaml") 219 | source_path = os.path.join(location, name + ".c") 220 | poc_path = os.path.join( 221 | location, "pocs", type_, "*" if poc_star else "", bin_name 222 | ) 223 | config_path = os.path.join(location, "analysis.yaml") 224 | bin_path = os.path.join(location, bin_name) 225 | 226 | check_single(result_path, bin_path, config_path) 227 | 228 | created_poc = create_poc_single( 229 | location, conf, bin_name, result_path, desc_path, source_path, poc_path 230 | ) 231 | self.assertTrue(created_poc) 232 | 233 | poc_worked = verify_poc_single(poc_path, type_, os.path.join(location, conf)) 234 | # poc creation is a tedious thing and in fact not relevant for angr's CI 235 | # self.assertTrue(poc_worked) 236 | 237 | 238 | def test_fastbin_dup(self): 239 | self.do_test("fastbin_dup", "malloc_non_heap") 240 | 241 | @flaky(max_runs=3, min_passes=1) 242 | @unittest.skip("broken") 243 | def test_house_of_lore(self): 244 | self.do_test("house_of_lore", "malloc_non_heap") 245 | 246 | def test_house_of_spirit(self): 247 | self.do_test("house_of_spirit", "malloc_non_heap") 248 | 249 | def test_overlapping_chunks(self): 250 | self.do_test("overlapping_chunks", "malloc_allocated") 251 | 252 | def test_unsorted_bin_attack(self): 253 | self.do_test("unsorted_bin_attack", "arbitrary_write_malloc", poc_star=True) 254 | 255 | def test_unsafe_unlink(self): 256 | self.do_test("unsafe_unlink", "arbitrary_write_free", poc_star=True) 257 | test_unsafe_unlink.speed = "slow" 258 | 259 | @unittest.skip("broken") 260 | def test_house_of_einherjar(self): 261 | self.do_test("house_of_einherjar", "malloc_non_heap") 262 | 263 | def test_poison_null_byte(self): 264 | self.do_test("poison_null_byte", "malloc_allocated") 265 | 266 | def test_tcache_poisoning(self): 267 | self.do_test("tcache_poisoning", "malloc_non_heap") 268 | 269 | 270 | if __name__ == "__main__": 271 | unittest.main() 272 | -------------------------------------------------------------------------------- /heaphopper/analysis/heap_condition_tracker.py: -------------------------------------------------------------------------------- 1 | from angr import SimProcedure 2 | from angr.state_plugins import SimStatePlugin, inspect 3 | import claripy 4 | import logging 5 | 6 | logger = logging.getLogger('HeapConditionTracker') 7 | 8 | 9 | class HeapConditionTracker(SimStatePlugin): 10 | def widen(self, _others): 11 | pass 12 | 13 | def __init__(self, config=None, libc=None, allocator=None, initialized=0, vulnerable=False, vuln_state=None, 14 | vuln_type='', malloc_dict=None, free_dict=None, write_bps=None, wtarget=None, 15 | req_size=None, arb_write_info=None, double_free=None, fake_frees=None, stack_trace=None, 16 | ctrl_data_idx=0, curr_freed_chunk=None, sym_data_states=None, sym_data_size=None, **kwargs): # pylint:disable=unused-argument 17 | super(HeapConditionTracker, self).__init__() 18 | self.config = config 19 | self.libc = libc 20 | self.allocator = allocator 21 | self.initialized = initialized 22 | self.vulnerable = vulnerable 23 | self.vuln_state = vuln_state 24 | self.vuln_type = vuln_type 25 | self.malloc_dict = dict() if malloc_dict is None else dict(malloc_dict) 26 | self.free_dict = dict() if free_dict is None else dict(free_dict) 27 | self.double_free = list() if double_free is None else list(double_free) 28 | self.fake_frees = list() if fake_frees is None else list(fake_frees) 29 | self.write_bps = list() if write_bps is None else list(write_bps) 30 | self.wtarget = wtarget 31 | self.req_size = req_size 32 | self.arb_write_info = dict() if arb_write_info is None else dict(arb_write_info) 33 | # Stack-trace of arb-write 34 | self.stack_trace = list() if stack_trace is None else list(stack_trace) 35 | # Counter for malloc dests 36 | self.ctrl_data_idx = ctrl_data_idx 37 | # Current object passed to free info 38 | self.curr_freed_chunk = curr_freed_chunk 39 | self.sym_data_states = dict() if sym_data_states is None else dict(sym_data_states) 40 | self.sym_data_size = sym_data_size 41 | 42 | def set_level(self, level): # pylint:disable=no-self-use 43 | logger.setLevel(level) 44 | 45 | @SimStatePlugin.memo 46 | def copy(self, _memo): 47 | return HeapConditionTracker(**self.__dict__) 48 | 49 | #def set_state(self, s, **kwargs): 50 | # super(HeapConditionTracker, self).set_state(s, **kwargs) 51 | 52 | # we need that for veritesting 53 | def merge(self, others, merge_conditions, common_ancestor=None): # pylint:disable=unused-argument 54 | # TODO: Do better merging 55 | for o in others: 56 | self.vulnerable |= o.vulnerable 57 | 58 | if not self.vuln_type and o.vuln_type: 59 | self.vuln_type = o.vuln_type 60 | 61 | if not self.malloc_dict and o.malloc_dict: 62 | self.malloc_dict = dict(o.malloc_dict) 63 | 64 | if not self.free_dict and o.free_dict: 65 | self.free_dict = dict(o.free_dict) 66 | 67 | if not self.arb_write_info and o.arb_write_info: 68 | self.arb_write_info = dict(o.arb_write_info) 69 | 70 | if not self.write_bps and o.write_bps: 71 | self.write_bps = o.write_bps 72 | 73 | if not self.wtarget and o.wtarget: 74 | self.wtarget = o.wtarget 75 | 76 | if not self.req_size and o.req_size: 77 | self.req_size = o.req_size 78 | 79 | if not self.double_free and o.double_free: 80 | self.double_free = list(o.double_free) 81 | 82 | if not self.vuln_state and o.vuln_state: 83 | self.vuln_state = o.vuln_state.copy() 84 | 85 | if not self.fake_frees and o.fake_frees: 86 | self.fake_frees = list(o.fake_frees) 87 | 88 | if not self.stack_trace and o.stack_trace: 89 | self.stack_trace = list(o.stack_trace) 90 | 91 | if not self.ctrl_data_idx and o.ctrl_data_idx: 92 | self.ctrl_data_idx = o.ctrl_data_idx 93 | 94 | if o.initialized: 95 | self.initialized = o.initialized 96 | 97 | return True 98 | 99 | 100 | class MallocInspect(SimProcedure): 101 | IS_FUNCTION = True 102 | local_vars = () 103 | 104 | def run(self, size, malloc_addr=None, vulns=None, ctrl_data=None): # pylint: disable=arguments-differ,unused-argument 105 | if 'arb_write' in vulns: 106 | self.state.heaphopper.write_bps.append(self.state.inspect.b('mem_write', when=inspect.BP_BEFORE, 107 | action=check_write)) 108 | self.state.heaphopper.req_size = size 109 | self.call(malloc_addr, (size,), 'check_malloc', prototype='void *malloc(size_t)') 110 | 111 | def check_malloc(self, size, malloc_addr, vulns=None, ctrl_data=None): #pylint:disable=unused-argument 112 | # Clear breakpoints 113 | for bp in self.state.heaphopper.write_bps: 114 | self.state.inspect.remove_breakpoint('mem_write', bp=bp) 115 | self.state.heaphopper.write_bps = [] 116 | 117 | # Don't track heap_init malloc 118 | if self.state.heaphopper.initialized == 0: 119 | self.state.heaphopper.initialized = 1 120 | return self.state.regs.rax 121 | 122 | if 'arb_write' in vulns: 123 | # Remove breakpoint and check for arbitrary writes 124 | if self.state.heaphopper.vuln_type == 'arbitrary_write': 125 | #logger.info('Found arbitrary write') 126 | self.state.heaphopper.vulnerable = True 127 | self.state.heaphopper.vuln_type = 'arbitrary_write_malloc' 128 | 129 | # Check if malloc return some bogus pointer 130 | addr = self.state.regs.rax 131 | 132 | # Get ctrl_data ptr used as id 133 | dict_key = ctrl_data[self.state.heaphopper.ctrl_data_idx] 134 | self.state.heaphopper.ctrl_data_idx += 1 135 | 136 | # This might be better than checking for symbolic 137 | sols = self.state.solver.eval_upto(addr, 2) 138 | if len(sols) > 1: 139 | return self.check_sym_malloc(addr, vulns, dict_key) 140 | 141 | # Get min val 142 | val = sols[0] 143 | self.state.add_constraints(addr == val) 144 | 145 | if self.state.heaphopper.vulnerable: 146 | return val 147 | 148 | if 'bad_alloc' in vulns and val in self.state.heaphopper.fake_frees: 149 | logger.info('Found allocation to fake freed address') 150 | self.state.add_constraints(addr == val) 151 | self.state.heaphopper.vulnerable = True 152 | self.state.heaphopper.vuln_type = 'malloc_non_heap' 153 | self.state.heaphopper.vuln_state = self.state.copy() 154 | elif 'bad_alloc' in vulns and val < self.state.heap.heap_base: 155 | logger.info('Found allocation on bogus non-heap address') 156 | self.state.add_constraints(addr < self.state.heap.heap_base) 157 | self.state.heaphopper.vulnerable = True 158 | self.state.heaphopper.vuln_type = 'malloc_non_heap' 159 | self.state.heaphopper.vuln_state = self.state.copy() 160 | elif 'overlap_alloc' in vulns and val not in self.state.heaphopper.double_free: 161 | if self.check_overlap(self.state.heaphopper.malloc_dict, addr, self.state.heaphopper.req_size): 162 | return val 163 | 164 | self.state.heaphopper.malloc_dict[dict_key] = (self.state.heaphopper.req_size, addr) 165 | 166 | # Remove from free dict if reallocated 167 | for key in list(self.state.heaphopper.free_dict.keys()): 168 | sol = self.state.solver.min(self.state.heaphopper.free_dict[key][1]) 169 | if val == sol: 170 | self.state.heaphopper.free_dict.pop(key) 171 | break 172 | 173 | return val 174 | 175 | def check_sym_malloc(self, addr, vulns, dict_key): 176 | if self.state.heaphopper.vulnerable: 177 | return addr 178 | 179 | # check non-heap: 180 | if 'bad_alloc' in vulns and self.state.solver.satisfiable( 181 | extra_constraints=[addr < self.state.heap.heap_base]): 182 | logger.info('Found allocation on bogus non-heap address') 183 | self.state.add_constraints(addr < self.state.heap.heap_base) 184 | val = self.state.solver.eval(addr) 185 | self.state.add_constraints(addr == val) 186 | self.state.heaphopper.vulnerable = True 187 | self.state.heaphopper.vuln_type = 'malloc_non_heap' 188 | self.state.heaphopper.vuln_state = self.state.copy() 189 | return addr 190 | 191 | # check overlaps 192 | # if the ast grows to big, str(addr) is expensive 193 | logger.info("check_sym_malloc: addr.ast.depth = %d", addr.to_claripy().depth) 194 | if 'overlap_alloc' in vulns and (addr.to_claripy().depth > 30 or \ 195 | str(addr) not in self.state.heaphopper.double_free): 196 | if self.check_overlap(self.state.heaphopper.malloc_dict, addr, self.state.heaphopper.req_size): 197 | return addr 198 | 199 | val = self.state.solver.min(addr) 200 | 201 | self.state.heaphopper.malloc_dict[dict_key] = (self.state.heaphopper.req_size, addr) 202 | 203 | ## This improves speed significantly, nobody knows why... some magic caching probably 204 | #size_vals = self.state.solver.eval_upto(self.state.heaphopper.req_size, 16) 205 | ## Let's use the value for smth. useful, if we solve anyways 206 | #if len(size_vals) == 1: 207 | # self.state.add_constraints(self.state.heaphopper.req_size == size_vals[0]) 208 | 209 | # Remove from free dict if reallocated 210 | for key in list(self.state.heaphopper.free_dict.keys()): 211 | sol = self.state.solver.min(self.state.heaphopper.free_dict[key][1]) 212 | if val == sol: 213 | self.state.heaphopper.free_dict.pop(key) 214 | break 215 | 216 | return addr 217 | 218 | def check_overlap(self, malloc_dict, addr, req_size): 219 | for dst in list(malloc_dict.keys()): 220 | alloc = malloc_dict[dst][1] 221 | condition1 = self.state.solver.And(alloc < addr, alloc + malloc_dict[dst][0] > addr) 222 | condition2 = self.state.solver.And(alloc > addr, addr + req_size > alloc) 223 | if self.state.solver.satisfiable(extra_constraints=[condition1]): 224 | logger.info('Found overlapping allocation') 225 | self.state.add_constraints(condition1) 226 | self.state.heaphopper.vulnerable = True 227 | self.state.heaphopper.vuln_type = 'malloc_allocated' 228 | self.state.heaphopper.vuln_state = self.state.copy() 229 | return True 230 | if self.state.solver.satisfiable(extra_constraints=[condition2]): 231 | logger.info('Found overlapping allocation') 232 | self.state.add_constraints(condition2) 233 | self.state.heaphopper.vulnerable = True 234 | self.state.heaphopper.vuln_type = 'malloc_allocated' 235 | self.state.heaphopper.vuln_state = self.state.copy() 236 | return True 237 | return False 238 | 239 | 240 | class FreeInspect(SimProcedure): 241 | IS_FUNCTION = True 242 | 243 | def run(self, ptr, free_addr=None, vulns=None, sym_data=None): # pylint: disable=arguments-differ 244 | val = self.state.solver.min(ptr) 245 | if val in sym_data: 246 | self.state.heaphopper.fake_frees.append(val) 247 | else: 248 | found = False 249 | for key in list(self.state.heaphopper.malloc_dict.keys()): 250 | sol = self.state.solver.min(self.state.heaphopper.malloc_dict[key][1]) 251 | if val == sol: 252 | minfo = self.state.heaphopper.malloc_dict.pop(key) 253 | self.state.heaphopper.free_dict[key] = minfo 254 | found = True 255 | if not found: 256 | for key in list(self.state.heaphopper.free_dict.keys()): 257 | sol = self.state.solver.min(self.state.heaphopper.free_dict[key][1]) 258 | if val == sol: 259 | self.state.heaphopper.double_free.append(val) 260 | 261 | if 'arb_write' in vulns: 262 | self.state.heaphopper.write_bps.append(self.state.inspect.b('mem_write', when=inspect.BP_BEFORE, 263 | action=check_write)) 264 | 265 | if val in sym_data: 266 | self.state.heaphopper.write_bps.append(self.state.inspect.b('mem_write', when=inspect.BP_BEFORE, 267 | action=check_sym_data)) 268 | self.state.heaphopper.curr_freed_chunk = val 269 | 270 | self.call(free_addr, [ptr], 'check_free', prototype='void free(void*)') 271 | 272 | def check_free(self, ptr, free_addr, vulns=None, sym_data=None): #pylint:disable=unused-argument 273 | # Clear the chunk currently being freed 274 | self.state.curr_freed_chunk = None 275 | 276 | # Clear breakpoints 277 | for bp in self.state.heaphopper.write_bps: 278 | self.state.inspect.remove_breakpoint('mem_write', bp=bp) 279 | self.state.heaphopper.write_bps = [] 280 | 281 | # Don't track heap_init free 282 | if self.state.heaphopper.initialized == 1: 283 | self.state.heaphopper.initialized = 2 284 | return 285 | 286 | # check non-heap: 287 | if 'bad_free' in vulns and self.state.solver.satisfiable( 288 | extra_constraints=[ptr < self.state.heap.heap_base]): 289 | logger.info('Found free of non-heap address') 290 | self.state.heaphopper.vulnerable = True 291 | self.state.heaphopper.vuln_type = 'free_non_heap' 292 | self.state.heaphopper.vuln_state = self.state.copy() 293 | return 294 | 295 | if 'double_free' in vulns: 296 | val = self.state.solver.min(ptr) 297 | if val in self.state.heaphopper.double_free: 298 | logger.info('Found double free') 299 | self.state.heaphopper.vulnerable = True 300 | self.state.heaphopper.vuln_state = self.state.copy() 301 | return 302 | 303 | if 'arb_write' not in vulns: 304 | return 305 | 306 | # Remove breakpoint and check for arbitrary writes 307 | if self.state.heaphopper.vuln_type == 'arbitrary_write': 308 | #logger.info('Found arbitrary write') 309 | self.state.heaphopper.vulnerable = True 310 | self.state.heaphopper.vuln_type = 'arbitrary_write_free' 311 | 312 | return 313 | 314 | 315 | def check_write(state): 316 | # If we found an arb_write we're done 317 | if state.heaphopper.vuln_type.startswith('arbitrary_write'): 318 | return 319 | 320 | # Check if we have an arbitrary_write 321 | addr = state.inspect.mem_write_address 322 | val = state.inspect.mem_write_expr 323 | #logger.debug('check_write: addr: %s' % addr) 324 | #logger.debug('check_write: val: %s' % val) 325 | constr = claripy.And(addr >= state.heaphopper.wtarget[0], 326 | addr < state.heaphopper.wtarget[0] + state.heaphopper.wtarget[1]) 327 | 328 | if state.solver.satisfiable(extra_constraints=[constr]): 329 | #logger.debug('check_write: Found arbitrary write') 330 | state.add_constraints(constr) 331 | state.heaphopper.vuln_state = state.copy() 332 | state.heaphopper.arb_write_info = dict(instr=state.addr, addr=addr, val=val) 333 | state.heaphopper.vuln_type = 'arbitrary_write' 334 | state.heaphopper.stack_trace = get_libc_stack_trace(state) 335 | 336 | def check_sym_data(state): 337 | # Check if we overwrite a sym_data structure passed to free 338 | addr = state.inspect.mem_write_address 339 | free_addr = state.heaphopper.curr_freed_chunk 340 | if free_addr in state.heaphopper.sym_data_states and not state.heaphopper.sym_data_states[free_addr]: 341 | #logger.debug('check_sym_data: curr_freed_chunk: 0x%x' % free_addr) 342 | #logger.debug('check_sym_data: addr: %s' % addr) 343 | constr = claripy.And(addr >= free_addr, addr < free_addr + state.heaphopper.sym_data_size) 344 | if not state.solver.symbolic(addr) and state.solver.satisfiable(extra_constraints=[constr]): 345 | #logger.debug('check_sym_data: Saving state') 346 | state.heaphopper.sym_data_states[free_addr] = state.copy() 347 | 348 | 349 | 350 | 351 | def get_libc_stack_trace(state): 352 | backtrace = state.history.bbl_addrs.hardcopy[::-1] 353 | index = 0 354 | while state.heaphopper.allocator.contains_addr(backtrace[index]): 355 | index += 1 356 | return backtrace[:index] 357 | -------------------------------------------------------------------------------- /heaphopper/gen/gen_zoo.py: -------------------------------------------------------------------------------- 1 | from os import path, mkdir 2 | from math import ceil, log 3 | from copy import deepcopy 4 | from json import dump 5 | 6 | import sys 7 | 8 | import re 9 | 10 | from os.path import basename, dirname, abspath 11 | 12 | from ..utils.parse_config import parse_config 13 | 14 | 15 | def includes(): 16 | return '\n'.join([ 17 | '#include ', 18 | '#include ', 19 | '#include ', 20 | '#include ', 21 | '#include ', 22 | '#include \n' 23 | '#include ' 24 | ]) 25 | 26 | 27 | def ctrled_struct(): 28 | return '\n'.join([ 29 | 'typedef struct __attribute__((__packed__)) {', 30 | '\tuint64_t * global_var;', 31 | '} controlled_data;\n' 32 | ]) 33 | 34 | 35 | def sym_struct(size): 36 | return '\n'.join([ 37 | 'typedef struct __attribute__((__packed__)) {', 38 | '\tuint8_t data[{}];'.format(size), 39 | '} symbolic_data;\n' 40 | ]) 41 | 42 | 43 | def winning(): 44 | return '\n'.join([ 45 | 'void winning(void) {', 46 | '\tputs("You win!");', 47 | '}\n' 48 | ]) 49 | 50 | 51 | def header_size(): 52 | return '\n'.join([ 53 | 'size_t header_size;' 54 | ]) 55 | 56 | 57 | def mem2chunk_offset(): 58 | return '\n'.join([ 59 | 'size_t mem2chunk_offset;' 60 | ]) 61 | 62 | 63 | def write_target(wtarget_size): 64 | return '\n'.join([ 65 | 'size_t write_target[{}];'.format(wtarget_size) 66 | ]) 67 | 68 | 69 | def offset(): 70 | return '\n'.join([ 71 | 'size_t offset;' 72 | ]) 73 | 74 | 75 | def malloc_sizes(count): 76 | return '\n'.join([ 77 | 'size_t malloc_sizes[{}];'.format(count) 78 | ]) 79 | 80 | 81 | def fill_sizes(count): 82 | return '\n'.join([ 83 | 'size_t fill_sizes[{}];'.format(count) 84 | ]) 85 | 86 | 87 | def overflow_sizes(count): 88 | return '\n'.join([ 89 | 'size_t overflow_sizes[{}];'.format(count) 90 | ]) 91 | 92 | 93 | def bitflip_offsets(count): 94 | return '\n'.join([ 95 | 'size_t bf_offsets[{}];'.format(count) 96 | ]) 97 | 98 | 99 | def arb_write_offsets(count): 100 | return '\n'.join([ 101 | 'size_t arw_offsets[{}];'.format(count) 102 | ]) 103 | 104 | 105 | def ctrled_data(count): 106 | cdata = [] 107 | for i in range(count): 108 | cdata.append('controlled_data __attribute__((aligned(16))) ctrl_data_{};'.format(i)) 109 | 110 | return '\n'.join(cdata) 111 | 112 | 113 | def symbolic_data(has_fake_free): 114 | data = '' 115 | if has_fake_free: 116 | data = '\n'.join([ 117 | 'symbolic_data __attribute__((aligned(16))) sym_data;' 118 | ]) 119 | return data 120 | 121 | 122 | # bin_info['allocs'] -> list of (dst_symbol, size_symbol) 123 | def malloc(num): 124 | fill_code, fill_desc = fill_chunk(num) 125 | code = '\n'.join([ 126 | '\t// Allocation', 127 | '\tctrl_data_{}.global_var = malloc(malloc_sizes[{}]);'.format(num, num), 128 | fill_code 129 | ]) 130 | desc = {'allocs': [('ctrl_data_{}.global_var'.format(num), 'malloc_sizes[{}]'.format(num))]} 131 | desc.update(fill_desc) 132 | return code, desc 133 | 134 | 135 | # bin_info['reads'] -> list of (dst_symbol, size_symbol) 136 | def fill_chunk(num): 137 | code = '\n'.join([ 138 | '\tfor (int i=0; i < fill_sizes[{}]; i+=8) {{'.format(num), 139 | '\t\tread(0, ((uint8_t *)ctrl_data_{}.global_var)+i, 8);'.format(num), 140 | '\t}\n' 141 | ]) 142 | desc = {'reads': [('ctrl_data_{}.global_var'.format(num), 'fill_sizes[{}]'.format(num))]} 143 | return code, desc 144 | 145 | 146 | # bin_info['frees'] -> list of (dst_symbol) 147 | def free(num): 148 | code = '\n'.join([ 149 | '\t// Free', 150 | '\tfree(ctrl_data_{}.global_var);\n'.format(num) 151 | ]) 152 | desc = {'frees': ['ctrl_data_{}.global_var'.format(num)]} 153 | return code, desc 154 | 155 | 156 | # bin_info['overflows'] -> list of (src_symbol, dst_symbol, size_symbol) 157 | def overflow(num, overflow_num): 158 | code = '\n'.join([ 159 | '\t// VULN: Overflow', 160 | # '\toffset = malloc_usable_size(ctrl_data_{}.global_var)-0x8;'.format(num), 161 | '\toffset = mem2chunk_offset;', 162 | '\tread({}, ((char *) ctrl_data_{}.global_var)-offset, overflow_sizes[{}]);\n'.format(FD, num + 1, overflow_num) 163 | ]) 164 | desc = {'overflows': [('ctrl_data_{}.global_var'.format(num), 165 | 'ctrl_data_{}.global_var'.format(num + 1), 166 | 'overflow_sizes[{}]'.format(overflow_num))]} 167 | return code, desc 168 | 169 | 170 | # bin_info['uafs'] -> list of (src_symbol, size_symbol) 171 | def uaf(num): 172 | code = '\n'.join([ 173 | '\t// VULN: UAF', 174 | '\tread({}, ctrl_data_{}.global_var, header_size);\n'.format(FD, num, num) 175 | ]) 176 | desc = {'uaf': [('ctrl_data_{}.global_var'.format(num), 'header_size')]} 177 | return code, desc 178 | 179 | 180 | # bin_info['fake_free'] -> list of fake_chunk_ptrs 181 | def fake_free(): 182 | code = '\n'.join([ 183 | '\t// VULN: Free fake chunk', 184 | '\tfree(((uint8_t *) &sym_data.data) + mem2chunk_offset);\n' 185 | ]) 186 | desc = {'fake_frees': ['sym_data.data']} 187 | return code, desc 188 | 189 | 190 | def arb_relative_write(num, count): 191 | code = '\n'.join([ 192 | '\t// VULN: Arbitrary relative write', 193 | '\tarw_offsets[{}] = 0;'.format(count), 194 | '\tread(0, &arw_offsets[{}], sizeof(arw_offsets[{}]));'.format(count, count), 195 | '\tarw_offsets[{}] = arw_offsets[{}] % malloc_sizes[{}];'.format(count, count, num), 196 | '\tread({}, ctrl_data_{}.global_var+arw_offsets[{}],' 197 | ' sizeof(arw_offsets[{}]));\n'.format(FD, num, count, count) 198 | ]) 199 | desc = {'arb_relative_write': [('ctrl_data_{}.global_var'.format(num), 'arw_offsets[{}]'.format(count))]} 200 | return code, desc 201 | 202 | 203 | def single_bitflip(num, count): 204 | code = '\n'.join([ 205 | '\t// VULN: Single bitflip', 206 | '\tbf_offsets[{}] = 0;'.format(count), 207 | '\tread(0, &bf_offsets[{}], sizeof(bf_offsets[{}]));'.format(count, count), 208 | '\tuint8_t bit_{};'.format(count), 209 | '\tread(0, &bit_{}, sizeof(bit_{}));'.format(count, count), 210 | '\tbit_{} = bit_{} % 64;'.format(count, count), 211 | '\t*(ctrl_data_{}.global_var+bf_offsets[{}]) =' 212 | ' *(ctrl_data_{}.global_var+bf_offsets[{}]) ^ (1 << bit_{});'.format(num, count, num, count, count) 213 | ]) 214 | desc = {'single_bitflip': [('ctrl_data_{}.global_var'.format(num), 'bf_offsets[{}]'.format(count), 215 | 'bit_{}'.format(count))]} 216 | return code, desc 217 | 218 | 219 | def double_free(num): 220 | code = '\n'.join([ 221 | '\t// VULN: Double free', 222 | '\tfree(ctrl_data_{}.global_var);'.format(num) 223 | ]) 224 | desc = {'double_free': [('ctrl_data_{}.global_var'.format(num),)]} 225 | return code, desc 226 | 227 | 228 | def main_start(): 229 | return '\n'.join([ 230 | '\nint main(void) {\n', 231 | ]) 232 | 233 | 234 | def main_end(): 235 | return '\n'.join([ 236 | '\twinning();', 237 | '\treturn 0;', 238 | '}' 239 | ]) 240 | 241 | 242 | def heap_setup(): 243 | return '\n'.join([ 244 | '\tvoid *dummy_chunk = malloc(0x200);', 245 | '\tfree(dummy_chunk);' 246 | ]) 247 | 248 | 249 | def mcheck(): 250 | return '\n'.join([ 251 | '\tmcheck(NULL);' # call mcheck for malloc hardening 252 | ]) 253 | 254 | 255 | def mcheck_pedantic(): 256 | return '\n'.join([ 257 | '\tmcheck_pedantic(NULL);' # call mcheck for malloc hardening 258 | ]) 259 | 260 | 261 | # Zoo generation 262 | ACTIONS = [('malloc', malloc), ('free', free), ('overflow', overflow), ('fake_free', fake_free), 263 | ('arb_relative_write', arb_relative_write), ('single_bitflip', single_bitflip), ('double_free', double_free), 264 | ('uaf', uaf)] 265 | 266 | FD = None 267 | 268 | 269 | # oh boy this is dirty 270 | def build_actions(zoo_actions, depth): 271 | global ACTIONS 272 | ACTIONS = [a for a in ACTIONS if a[0] in list(zoo_actions.keys())] 273 | action_counts = {} 274 | final_actions = [] 275 | for action in ACTIONS: 276 | action_counts[action[0]] = 0 277 | count = zoo_actions[action[0]] 278 | if count == -1: 279 | action += (depth,) 280 | else: 281 | action += (count,) 282 | final_actions.append(action) 283 | ACTIONS = final_actions 284 | return action_counts 285 | 286 | 287 | def gen_variants(config, action_counts): 288 | global FD 289 | FD = config['mem_corruption_fd'] 290 | variants = [] 291 | descs = [] 292 | v = [] 293 | d = {'allocs': [], 'frees': [], 'reads': [], 'overflows': [], 'uafs': [], 'fake_frees': [], 294 | 'arb_relative_writes': [], 295 | 'single_bitflips': [], 'double_frees': []} 296 | vuln_states = {'overflow_cnt': 0, 'pend_overf': [], 'uaf': [], 'freed_fake': [], 'arb_write_cnt': 0, 297 | 'bitflip_cnt': 0} 298 | total_count = add_variants(v, d, variants, descs, config['zoo_depth'], [], [], vuln_states, 299 | action_counts) # depth is 0-based 300 | print("Depth: {}".format(config['zoo_depth'])) 301 | print("Total number of permutations: {}".format(total_count)) 302 | return variants, descs 303 | 304 | 305 | variant = 0 306 | 307 | 308 | def add_variants(v, d, variants, descs, depth, chunks, frees, vuln_states, action_counts): 309 | global variant 310 | total_count = 0 311 | if depth == 1: 312 | for name, action, count in ACTIONS: 313 | if name == 'malloc': 314 | if vuln_states['overflow_cnt'] == 0 and vuln_states['arb_write_cnt'] == 0 \ 315 | and vuln_states['bitflip_cnt'] == 0 and not vuln_states['freed_fake'] \ 316 | and len(frees) == len(set(frees)) and len(vuln_states['uaf']) == 0: # no need to trace benign 317 | continue 318 | code, desc = action(len(d['allocs'])) 319 | new_d = deepcopy(d) 320 | new_d['allocs'].extend(desc['allocs']) 321 | new_d['reads'].extend(desc['reads']) 322 | new_v = list(v) 323 | new_v.append(code) 324 | new_chunks = list(chunks) 325 | new_chunks.append(len(chunks)) 326 | variants.append((new_v, len(new_chunks), vuln_states)) 327 | descs.append(new_d) 328 | variant += 1 329 | total_count += 1 330 | elif name == 'free': 331 | for chunk in chunks: 332 | code, desc = action(chunk) 333 | new_d = deepcopy(d) 334 | new_d['frees'].extend(desc['frees']) 335 | new_v = list(v) 336 | new_v.append(code) 337 | if vuln_states['overflow_cnt'] == 0 and vuln_states['arb_write_cnt'] == 0 \ 338 | and vuln_states['bitflip_cnt'] == 0 and not vuln_states['freed_fake'] \ 339 | and len(frees) == len(set(frees)): # no need to trace benign 340 | continue 341 | variants.append((new_v, len(chunks), vuln_states)) 342 | descs.append(new_d) 343 | variant += 1 344 | total_count += 1 345 | elif name == 'overflow': 346 | continue 347 | elif name == 'uaf': 348 | continue 349 | elif name == 'fake_free': 350 | continue 351 | elif name == 'arb_relative_write': 352 | continue 353 | elif name == 'single_bitflip': 354 | continue 355 | elif name == 'double_free': 356 | continue 357 | return total_count 358 | 359 | for name, action, count in ACTIONS: 360 | if name == 'malloc': 361 | if action_counts[name] >= count: 362 | continue 363 | code, desc = action(len(d['allocs'])) 364 | new_d = deepcopy(d) 365 | new_d['allocs'].extend(desc['allocs']) 366 | new_d['reads'].extend(desc['reads']) 367 | new_v = list(v) 368 | new_v.append(code) 369 | new_chunks = list(chunks) 370 | new_chunks.append(len(chunks)) 371 | new_vuln_states = deepcopy(vuln_states) 372 | new_vuln_states['pend_overf'] = [] 373 | new_action_counts = dict(action_counts) 374 | new_action_counts[name] += 1 375 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, new_chunks, frees, 376 | new_vuln_states, new_action_counts) 377 | elif name == 'free': 378 | if action_counts[name] >= count: 379 | continue 380 | for chunk in chunks: 381 | if chunk in frees: 382 | continue 383 | code, desc = action(chunk) 384 | new_d = deepcopy(d) 385 | new_d['frees'].extend(desc['frees']) 386 | new_v = list(v) 387 | new_v.append(code) 388 | new_frees = list(frees) 389 | new_frees.append(chunk) 390 | new_vuln_states = deepcopy(vuln_states) 391 | new_vuln_states['pend_overf'] = [] 392 | new_action_counts = dict(action_counts) 393 | new_action_counts[name] += 1 394 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, chunks, new_frees, 395 | new_vuln_states, new_action_counts) 396 | elif name == 'overflow': 397 | if action_counts[name] >= count: 398 | continue 399 | for chunk in chunks[:-1]: # don't overflow from last 400 | if chunk in vuln_states['pend_overf']: # don't overflow a chunk twice without actions in between 401 | continue 402 | code, desc = action(chunk, vuln_states['overflow_cnt']) 403 | vuln_states['overflow_cnt'] += 1 404 | new_d = deepcopy(d) 405 | new_d['overflows'].extend(desc['overflows']) 406 | new_v = list(v) 407 | new_v.append(code) 408 | new_vuln_states = deepcopy(vuln_states) 409 | new_vuln_states['pend_overf'].append(chunk) 410 | new_action_counts = dict(action_counts) 411 | new_action_counts[name] += 1 412 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, chunks, frees, 413 | new_vuln_states, new_action_counts) 414 | elif name == 'uaf': 415 | if action_counts[name] >= count: 416 | continue 417 | for chunk in frees: 418 | if chunk in vuln_states['uaf']: # don't uaf a chunk twice without actions in between 419 | continue 420 | code, desc = action(chunk) 421 | new_d = deepcopy(d) 422 | new_d['uafs'].extend(desc['uaf']) 423 | new_v = list(v) 424 | new_v.append(code) 425 | new_vuln_states = deepcopy(vuln_states) 426 | new_vuln_states['uaf'].append(chunk) 427 | new_action_counts = dict(action_counts) 428 | new_action_counts[name] += 1 429 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, chunks, frees, 430 | new_vuln_states, new_action_counts) 431 | elif name == 'fake_free': 432 | if action_counts[name] >= count: 433 | continue 434 | if vuln_states['freed_fake']: # only free one fake chunk 435 | continue 436 | code, desc = action() 437 | new_d = deepcopy(d) 438 | new_d['fake_frees'].extend(desc['fake_frees']) 439 | new_v = list(v) 440 | new_v.append(code) 441 | new_vuln_states = deepcopy(vuln_states) 442 | new_vuln_states['freed_fake'] = True 443 | new_action_counts = dict(action_counts) 444 | new_action_counts[name] += 1 445 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, chunks, frees, 446 | new_vuln_states, new_action_counts) 447 | elif name == 'arb_relative_write': 448 | if action_counts[name] >= count: 449 | continue 450 | for chunk in chunks[:-1]: # don't write into the last 451 | code, desc = action(chunk, vuln_states['arb_write_cnt']) 452 | new_d = deepcopy(d) 453 | new_d['arb_relative_writes'].extend(desc['arb_relative_write']) 454 | new_v = list(v) 455 | new_v.append(code) 456 | new_vuln_states = deepcopy(vuln_states) 457 | new_vuln_states['arb_write_cnt'] += 1 458 | new_action_counts = dict(action_counts) 459 | new_action_counts[name] += 1 460 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, chunks, frees, 461 | new_vuln_states, new_action_counts) 462 | 463 | elif name == 'single_bitflip': 464 | if action_counts[name] >= count: 465 | continue 466 | for chunk in chunks[:-1]: # don't write into the last 467 | code, desc = action(chunk, vuln_states['bitflip_cnt']) 468 | new_d = deepcopy(d) 469 | new_d['single_bitflips'].extend(desc['single_bitflip']) 470 | new_v = list(v) 471 | new_v.append(code) 472 | new_vuln_states = deepcopy(vuln_states) 473 | new_vuln_states['bitflip_cnt'] += 1 474 | new_action_counts = dict(action_counts) 475 | new_action_counts[name] += 1 476 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, chunks, frees, 477 | new_vuln_states, new_action_counts) 478 | 479 | elif name == 'double_free': 480 | if action_counts[name] >= count: 481 | continue 482 | for chunk in frees: 483 | code, desc = action(chunk) 484 | new_d = deepcopy(d) 485 | new_d['double_frees'].extend(desc['double_free']) 486 | new_v = list(v) 487 | new_v.append(code) 488 | new_frees = list(frees) 489 | new_frees.append(chunk) 490 | new_action_counts = dict(action_counts) 491 | new_action_counts[name] += 1 492 | total_count += add_variants(new_v, new_d, variants, descs, depth - 1, chunks, new_frees, 493 | vuln_states, new_action_counts) 494 | 495 | return total_count 496 | 497 | 498 | def write_files(config, variants): 499 | fnames = [] 500 | 501 | if not len(variants): 502 | return fnames 503 | 504 | id_len = int(ceil(log(len(variants), 10))) 505 | 506 | if not path.isdir(config['zoo_dir']): 507 | mkdir(config['zoo_dir']) 508 | 509 | for i, v in enumerate(variants): 510 | file_name = "{}".format(str(i).rjust(id_len, '0')) 511 | with open('{}/{}.c'.format(config['zoo_dir'], file_name), 'w') as f: 512 | content = '\n'.join([ 513 | includes(), 514 | ctrled_struct(), 515 | sym_struct(config['sym_data_size']), 516 | winning(), 517 | write_target(config['wtarget_size']), 518 | offset(), 519 | header_size(), 520 | mem2chunk_offset(), 521 | malloc_sizes(v[1]), 522 | fill_sizes(v[1]), 523 | overflow_sizes(v[2]['overflow_cnt']), 524 | arb_write_offsets(v[2]['arb_write_cnt']), 525 | bitflip_offsets(v[2]['bitflip_cnt']), 526 | ctrled_data(v[1]), 527 | symbolic_data(v[2]['freed_fake']), 528 | main_start(), 529 | ]) 530 | 531 | if config['mcheck'] == 'enable': 532 | content += '\n'.join([mcheck()]) 533 | elif config['mcheck'] == 'pedantic': 534 | content += '\n'.join([mcheck_pedantic()]) 535 | 536 | content += '\n'.join([ 537 | heap_setup(), 538 | '\n'.join(v[0]), 539 | main_end() 540 | ]) 541 | f.write(content) 542 | fnames.append(file_name) 543 | return fnames 544 | 545 | 546 | def create_makefile(zoo_dir, fnames, allocator, libc): 547 | alloc_name = re.search(r'lib([\w]+).so', basename(allocator)).group(1) 548 | libc_name = re.search(r'lib([\w]+).so', basename(libc)).group(1) 549 | alloc_path = abspath(allocator) 550 | libc_path = abspath(libc) 551 | 552 | with open('{}/Makefile'.format(zoo_dir), 'w') as f: 553 | f.write('CC = gcc\n') 554 | f.write('CFLAGS += -std=c99 -g -O0\n') 555 | f.write('LDFLAGS += -no-pie\n') 556 | f.write('SOURCES = $(wildcard *.c)\n') 557 | f.write('OBJECTS = $(SOURCES:.c=.o)\n') 558 | f.write('BINARIES = {}\n'.format(' '.join(fnames))) 559 | f.write('DIRNAME = bin\n\n') 560 | f.write('.PHONY: all clean distclean gendir cpy_file\n\n') 561 | f.write('all: gendir $(BINARIES) cpy_file\n\n') 562 | f.write('clean:\n\trm $(OBJECTS)\n\n') 563 | f.write('distclean: clean\n\trm -r $(DIRNAME)\n\n') 564 | f.write('gendir:\n\tmkdir -p $(DIRNAME)\n\n') 565 | f.write('cpy_file:\n\tfor desc in *.desc; do cp $$desc bin/; done\n\n') 566 | f.write('%.o: %.c\n\t$(CC) $(CFLAGS) -c -o $@ $^\n\n') 567 | for file_name in fnames: 568 | f.write('{}: {}.o\n\t$(CC) -L{} -L{} -o "$(DIRNAME)/$@.bin" $^ $(LDFLAGS) -l{} -l{}\n\n'.format(file_name, 569 | file_name, 570 | dirname( 571 | alloc_path), 572 | dirname( 573 | libc_path), 574 | alloc_name, 575 | libc_name)) 576 | 577 | 578 | def create_descriptions(zoo_dir, descs, fnames): 579 | for desc, fname in zip(descs, fnames): 580 | with open('{}/{}.desc'.format(zoo_dir, fname), 'w') as f: 581 | dump(desc, f) 582 | 583 | 584 | def usage(argv): 585 | print('Usage: {} '.format(argv[0])) 586 | 587 | 588 | def gen_zoo(config_file): 589 | global ACTIONS 590 | config = parse_config(config_file) 591 | action_count = build_actions(config['zoo_actions'], config['zoo_depth']) 592 | variants, descs = gen_variants(config, action_count) 593 | print('Variants: {}'.format(len(variants))) 594 | if not config['create_files'] or not len(variants): 595 | return -1 596 | fnames = write_files(config, variants) 597 | create_makefile(config['zoo_dir'], fnames, config['allocator'], config['libc']) 598 | create_descriptions(config['zoo_dir'], descs, fnames) 599 | return 0 600 | 601 | 602 | if __name__ == '__main__': 603 | if len(sys.argv) < 1: 604 | usage(sys.argv) 605 | sys.exit(1) 606 | gen_zoo(sys.argv[1]) 607 | -------------------------------------------------------------------------------- /heaphopper/gen/gen_pocs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import binascii 3 | import re 4 | from hashlib import md5 5 | from math import log, ceil 6 | 7 | from os.path import basename, dirname, abspath 8 | 9 | import angr 10 | import logging 11 | import yaml 12 | import os 13 | from itertools import takewhile 14 | 15 | from ..utils.parse_config import parse_config 16 | 17 | logger = logging.getLogger('poc-generator') 18 | 19 | 20 | def load_yaml(file_name): 21 | with open(file_name, 'r') as f: 22 | result = yaml.load(f, Loader=yaml.SafeLoader) 23 | return result 24 | 25 | 26 | def gen_dir(exploit_path, file_name, vuln, stack_trace): 27 | if not os.path.isdir(exploit_path): 28 | os.mkdir(exploit_path) 29 | dir_name = "{}/{}".format(exploit_path, vuln) 30 | if not os.path.isdir(dir_name): 31 | os.mkdir(dir_name) 32 | if stack_trace: 33 | m = md5() 34 | for addr in stack_trace: 35 | m.update(str(addr).encode()) 36 | dir_name = "{}/stack_trace_{}".format(dir_name, m.hexdigest()) 37 | if not os.path.isdir(dir_name): 38 | os.mkdir(dir_name) 39 | dir_name = "{}/{}".format(dir_name, os.path.basename(file_name)) 40 | if not os.path.isdir(dir_name): 41 | os.mkdir(dir_name) 42 | return dir_name 43 | 44 | 45 | def check_addr(addr, next_part, prev_part, main_bin, heap_base, allocs, sym, sym_off): 46 | shift = 0 47 | masks = {0: 0x00, 1: 0xff, 2: 0xffff, 3: 0xffffff, 4: 0xffffffff, 5: 0xffffffffff, 6: 0xffffffffffff, 48 | 7: 0xffffffffffffff} 49 | masks_prev = {0: 0xffffffffffffffff, 1: 0xffffffffffffff00, 2: 0xffffffffffff0000, 3: 0xffffffffff000000, 50 | 4: 0xffffffff00000000, 51 | 5: 0xffffff0000000000, 6: 0xffff000000000000, 7: 0xff00000000000000} 52 | while shift < 8: 53 | actual_addr = ((next_part & masks[shift]) << (shift * 8)) | (addr >> (shift * 8)) 54 | section = main_bin.find_section_containing(actual_addr) 55 | if not section: 56 | if 0 <= actual_addr - heap_base < 0x80000: 57 | offset = actual_addr - allocs[0] 58 | chunk = (0, offset) 59 | for idx, alloc in enumerate(allocs): 60 | offset = actual_addr - alloc 61 | if 0 <= offset < chunk[1]: 62 | chunk = (idx, offset) 63 | if shift > 0: 64 | expr = ('((uint64_t) (((char *) ctrl_data_{}.global_var)' 65 | ' + {}) << {})' 66 | ' | {} | {}').format(chunk[0], hex(chunk[1]), shift * 8, 67 | hex((addr & masks_prev[prev_part[0]]) & 68 | masks[ 69 | shift]), 70 | prev_part[1]) 71 | next_part = (shift, next_part & masks[shift]) 72 | else: 73 | expr = '((char *) ctrl_data_{}.global_var) + {}'.format(chunk[0], hex(chunk[1])) 74 | next_part = (0, 0x0) 75 | return expr, next_part 76 | elif 'bss' in section.name: 77 | wtarget = main_bin.get_symbol('write_target') 78 | offset = actual_addr - wtarget.rebased_addr 79 | if shift > 0: 80 | expr = '((uint64_t) (((char *) &write_target) + {}) << {}) | {} | {}'.format(hex(offset), shift * 8, 81 | hex((addr & masks[ 82 | prev_part[0]]) & 83 | masks_prev[shift]), 84 | prev_part[1]) 85 | next_part = (shift, next_part & masks[shift]) 86 | else: 87 | expr = '((char *) &write_target) + {}'.format(hex(offset)) 88 | next_part = (0, 0x0) 89 | return expr, next_part 90 | shift += 1 91 | 92 | return check_offset(sym, sym_off, addr, main_bin, allocs) 93 | 94 | 95 | def check_offset(sym, sym_off, addr, main_bin, allocs): 96 | if addr < 0x40: 97 | return hex(addr), (0, 0x0) 98 | #if addr > 0x100000000000000: # Just in case it can't hurt to point back to us, pointer is arbitrary anyways 99 | # return '(uint64_t) &write_target', (0, 0x0) 100 | 101 | if not sym: 102 | return hex(addr), (0, 0x0) 103 | 104 | if 'ctrl_data' in sym: 105 | alloc_index = int(re.findall(r'ctrl_data_(\d+)', sym)[0]) 106 | base = allocs[alloc_index] + sym_off 107 | sym_prefix = '' 108 | else: 109 | symbol = main_bin.get_symbol(sym) 110 | if not symbol: 111 | return hex(addr), (0, 0x0) 112 | base = symbol.rebased_addr + sym_off 113 | sym_prefix = '&' 114 | 115 | addr_clean = addr & ~0x7 116 | for s in main_bin.symbols: 117 | 118 | if sym == s.name: 119 | continue 120 | if base + addr_clean >= s.rebased_addr and base + addr_clean < s.rebased_addr + s.size: 121 | if s.name != 'write_target': 122 | for i in range(0, 0x40, 8): 123 | found_sym = main_bin.loader.find_symbol(s.rebased_addr + i) 124 | if found_sym and found_sym.name == 'write_target': 125 | break 126 | 127 | if not found_sym: 128 | found_sym = s 129 | off = base + addr_clean - found_sym.rebased_addr 130 | expr = '(uint64_t) ((((char *) &{}) - (char *) {}{}) - {}) + {}'.format(found_sym.name, sym_prefix, sym, sym_off, off) 131 | return expr, (0, 0x0) 132 | elif base - addr_clean >= s.rebased_addr and base - addr_clean < s.rebased_addr + s.size: 133 | if s.name != 'write_traget': 134 | for i in range(0, 0x40, 8): 135 | found_sym = main_bin.loader.find_symbol(s.rebased_addr + i) 136 | if found_sym and found_sym.name == 'write_target': 137 | break 138 | 139 | if not found_sym: 140 | found_sym = s 141 | off = base - addr_clean - found_sym.rebased_addr 142 | expr = '(uint64_t) ((((char *) {}{}) - (char *) &{}) + {}) - {}'.format(sym_prefix, sym, found_sym.name, sym_off, off) 143 | return expr, (0, 0x0) 144 | 145 | for idx, alloc in enumerate(allocs): 146 | if base + addr_clean >= alloc and idx < len(allocs) - 1 and base + addr_clean < allocs[idx + 1]: 147 | off = base + addr_clean - alloc 148 | expr = '(uint64_t) ((((char *) ctrl_data_{}.global_var) - (char *) {}) - {}) + {}'.format(idx, sym, sym_off, 149 | off) 150 | return expr, (0, 0x0) 151 | elif base - addr_clean >= alloc and idx < len(allocs) - 1 and base - addr_clean < allocs[idx + 1]: 152 | off = base - addr_clean - alloc 153 | expr = '(uint64_t) ((((char *) {}) - (char *) ctrl_data_{}.global_var) + {}) - {}'.format(sym, idx, sym_off, 154 | off) 155 | return expr, (0, 0x0) 156 | 157 | if 'write_target' in sym: 158 | if 0x200 < addr < 0x2000: 159 | addr = addr & 1 160 | 161 | return hex(addr), (0, 0x0) 162 | 163 | 164 | def get_last_line(last_line, src_file): 165 | with open(src_file, 'r') as sf: 166 | content = sf.read().split('\n')[last_line - 1] 167 | return content 168 | 169 | 170 | def gen_poc(result, src_file, bin_file, last_line): 171 | global last_action_size, space 172 | with open('{}'.format(src_file), 'r') as f: 173 | lines = f.read().split('\n') 174 | 175 | main_bin = angr.Project(bin_file, auto_load_libs=False).loader.main_object 176 | heap_base = result['heap_base'] 177 | mem2chunk = result['mem2chunk_offset'] 178 | init_dict = dict() 179 | pocs = [] 180 | poc_descs = [] 181 | for input_opt, stdin_opt, svars, header, msizes, fsizes, osizes, wtargets, allocs, arw_offsets, bf_offsets in zip( 182 | result['input_opts'], 183 | result['stdin_opts'], 184 | result['symbolic_data'], 185 | result['header_sizes'], 186 | result['malloc_sizes'], 187 | result['fill_sizes'], 188 | result['overflow_sizes'], 189 | result['write_targets'], 190 | result['allocs'], 191 | result['arb_write_offsets'], 192 | result['bf_offsets']): 193 | 194 | if type(input_opt) == str: 195 | input_opt = bytes(input_opt, 'utf-8') 196 | 197 | poc = [] 198 | poc_desc = dict(allocs=0, frees=0, overflows=0, fake_frees=0, double_frees=0, arb_relative_writes=0, 199 | single_bitflips=0, uafs=0, constrained_target=False) 200 | free_list = [] 201 | iter_lines = iter(lines[:last_line]) 202 | for line in iter_lines: 203 | last_action_size = 0 204 | space = ''.join(takewhile(str.isspace, line)) 205 | if 'free(dummy_chunk)' in line: 206 | poc.append(line) 207 | poc.append('\t# if print\n\t\tprintf("Init printf: %p\\n", dummy_chunk);\n\t# endif\n') 208 | last_action_size += 2 209 | elif 'free(ctrl_data' in line: 210 | poc.append(line) 211 | dst = list(map(int, re.findall(r'ctrl_data_(\d+).global_var', line)))[0] 212 | if dst in free_list: 213 | poc_desc['double_frees'] += 1 214 | else: 215 | free_list.append(dst) 216 | poc_desc['frees'] += 1 217 | poc.append( 218 | '{}#if print\n{}\tprintf("Free: %p\\n", ctrl_data_{}.global_var);\n{}#endif\n'.format(space, 219 | space, 220 | dst, 221 | space)) 222 | last_action_size += 2 223 | elif line.strip().startswith('//'): # comment 224 | poc.append(line) 225 | last_action_size += 1 226 | elif ' = malloc(malloc_sizes' in line: # allocation 227 | last_action_size += 2 228 | poc_desc['allocs'] += 1 229 | dst, msize_index = list(map(int, re.findall(r'ctrl_data_(\d+).global_var = malloc\(malloc_sizes\[(\d+)\]\);', 230 | line)[0])) 231 | poc.append(line) 232 | poc.append( 233 | ('{}#if print\n{}\tprintf("Allocation: %p\\nSize: 0x%lx\\n",' 234 | 'ctrl_data_{}.global_var, malloc_sizes[{}]);\n{}#endif\n').format( 235 | space, 236 | space, 237 | dst, 238 | msize_index, 239 | space)) 240 | try: 241 | for_line = next(iter_lines) # get for 242 | next(iter_lines) # get read 243 | next(iter_lines) # get curly brace 244 | except StopIteration: 245 | continue 246 | if not len(stdin_opt): 247 | continue 248 | fsize_index = int(re.findall(r'fill_sizes\[(\d+)\]', for_line)[0]) 249 | prev_part = (0, 0x0) 250 | for i in range(0, fsizes[fsize_index], 8): 251 | val = b'0x' + binascii.hexlify(stdin_opt[:8][::-1]) 252 | stdin_opt = stdin_opt[8:] 253 | if len(stdin_opt) >= 8: 254 | next_part = int(b'0x' + binascii.hexlify(stdin_opt[:8][::-1]), 16) 255 | else: 256 | next_part = 0x0 257 | sym_offset, prev_part = check_addr(int(val, 16), next_part, prev_part, main_bin, heap_base, allocs, 258 | 'ctrl_data_{}.global_var'.format(dst), i) 259 | curr_dst = 'ctrl_data_{}.global_var[{}]'.format(dst, (i // 8)) 260 | instr = '{}{} = (uint64_t) {};'.format(space, curr_dst, sym_offset) 261 | if 'ctrl_data' in sym_offset: 262 | idx = int(re.findall(r'ctrl_data_(\d+)', sym_offset)[0]) 263 | if idx > dst: 264 | key = 'ctrl_data_{}'.format(idx) 265 | if key not in init_dict: 266 | init_dict[key] = [] 267 | init_dict[key].append(instr) 268 | continue 269 | 270 | poc.append(instr) 271 | last_action_size += 1 272 | key = 'ctrl_data_{}'.format(dst) 273 | if key in init_dict: 274 | poc.append('\n') 275 | last_action_size += 1 276 | for instr in init_dict[key]: 277 | poc.append(instr) 278 | last_action_size += 1 279 | elif 'read' in line and 'header_size);' in line: 280 | # UAF 281 | poc_desc['uafs'] += 1 282 | dst = re.findall(r'read\(.*, (ctrl_data_\d+.global_var),', line)[0] 283 | dst_idx = re.findall(r'ctrl_data_(\d+).global_var', dst)[0] 284 | for i in range(0, header, 8): 285 | val = b'0x' + binascii.hexlify(input_opt[:8][::-1]) 286 | input_opt = input_opt[8:] 287 | sym_offset, prev_part = check_addr(int(val, 16), 0x0, (0, 0x0), main_bin, heap_base, allocs, 288 | 'ctrl_data_{}.global_var'.format(dst_idx), i) 289 | poc.append('{}{}[{}] = {};'.format(space, dst, i // 8, sym_offset)) 290 | last_action_size += 1 291 | elif 'read(0, &arw_offsets' in line: 292 | # arbitrary relative write 293 | poc_desc['arb_relative_writes'] += 1 294 | offset_dst = re.findall(r'read\(0, &(arw_offsets\[\d+\]),', line)[0] 295 | val = b'0x' + binascii.hexlify(stdin_opt[:8][::-1]) 296 | stdin_opt = stdin_opt[8:] 297 | sym_offset, prev_part = check_addr(int(val, 16), 0x0, (0, 0x0), main_bin, heap_base, allocs, 298 | 'arw_offsets', (poc_desc['arb_relative_writes'] - 1) * 8) 299 | poc.append('{}{} = {};'.format(space, offset_dst, sym_offset)) 300 | last_action_size += 1 301 | 302 | line = next(iter_lines) # get second read 303 | read_dst = re.findall(r'read\(.*, (.*), .*\)', line)[0] 304 | read_base, read_offset = read_dst.split('+') 305 | val = b'0x' + binascii.hexlify(input_opt[:8][::-1]) 306 | input_opt = input_opt[8:] 307 | sym_offset, prev_part = check_addr(int(val, 16), 0x0, (0, 0x0), main_bin, heap_base, allocs, None, 0) 308 | poc.append('{}{}[{}] = (uint64_t) {};'.format(space, read_base, read_offset, sym_offset)) 309 | last_action_size += 1 310 | elif 'read(0, &bf_offsets' in line: 311 | # single bitflip 312 | poc_desc['single_bitflips'] += 1 313 | offset_dst = re.findall(r'read\(0, &(bf_offsets\[\d+\]),', line)[0] 314 | val = b'0x' + binascii.hexlify(stdin_opt[:8][::-1]) 315 | stdin_opt = stdin_opt[8:] 316 | sym_offset, prev_part = check_addr(int(val, 16), 0x0, (0, 0x0), main_bin, heap_base, allocs, 317 | 'bf_offsets', (poc_desc['single_bitflips'] - 1) * 8) 318 | poc.append('{}{} = {};'.format(space, offset_dst, sym_offset)) 319 | last_action_size += 1 320 | 321 | line = next(iter_lines) # get second read 322 | bit_dst = re.findall(r'read\(0, (bit_\d+), .*\)', line)[0] 323 | val = b'0x' + binascii.hexlify(stdin_opt[:1]) 324 | stdin_opt = stdin_opt[1:] 325 | sym_offset, prev_part = check_addr(int(val, 16), 0x0, (0, 0x0), main_bin, heap_base, allocs, None, 0) 326 | poc.append('{}{} = {};'.format(space, bit_dst, sym_offset)) 327 | last_action_size += 1 328 | elif 'read(' in line: 329 | # Overflow 330 | if not len(input_opt): 331 | continue 332 | poc_desc['overflows'] += 1 333 | dst, size = re.findall(r'read\(.*, (.*), ([A-Za-z0-9\[\]_\-]*)\)', line)[0] 334 | ctrl_data_index = re.findall(r'ctrl_data_(\d+)', dst)[0] 335 | index = int(re.findall(r'\[(\d*)\]', size)[0], 10) 336 | 337 | new_size = osizes[index] 338 | prev_part = (0, 0x0) 339 | for i in range(0, new_size, 8): 340 | if new_size - i < 8: 341 | break 342 | val = b'0x' + binascii.hexlify(input_opt[:8][::-1]) 343 | input_opt = input_opt[8:] 344 | if len(input_opt) >= 8: 345 | next_part = int(b'0x' + binascii.hexlify(input_opt[:8][::-1]), 16) 346 | else: 347 | next_part = 0x0 348 | sym_offset, prev_part = check_addr(int(val, 16), next_part, prev_part, main_bin, heap_base, allocs, 349 | 'ctrl_data_{}.global_var'.format(ctrl_data_index), -mem2chunk) 350 | curr_dst = '((uint64_t*) ({}))[{}]'.format(dst, (i // 8)) 351 | poc.append('{}{} = (uint64_t) {};'.format(space, curr_dst, sym_offset)) 352 | last_action_size += 1 353 | 354 | # remainder 355 | if new_size - (i + 8) < 8 and new_size - (i + 8) > 0: 356 | val = b'0x' + binascii.hexlify(input_opt[:new_size - (i + 8)][::-1]) 357 | bits = 2 ** int(ceil(log((len(val) - 2) * 4, 2))) 358 | input_opt = input_opt[new_size - (i + 8):] 359 | sym_offset, prev_part = check_addr(int(val, 16), 0x0, prev_part, main_bin, heap_base, allocs, 360 | 'ctrl_data_{}.global_var'.format(ctrl_data_index), i + 8) 361 | curr_dst = '((uint{}_t*) ({}+{}))[0]'.format(bits, dst, hex(i + 8)) 362 | poc.append('{}{} = (uint{}_t) {};'.format(space, curr_dst, bits, sym_offset)) 363 | last_action_size += 1 364 | 365 | 366 | elif line.startswith('controlled_data '): 367 | # we need go get rid of the trailing L 368 | poc.append(line) 369 | last_action_size += 1 370 | 371 | elif 'size_t malloc_sizes' in line: 372 | vals = [] 373 | prev_part = (0, 0x0) 374 | for idx, var in enumerate(msizes): 375 | if idx < len(msizes) - 1: 376 | next_part = msizes[idx + 1] 377 | else: 378 | next_part = 0x0 379 | sym_offset, prev_part = check_addr(var, next_part, prev_part, main_bin, heap_base, allocs, None, 0) 380 | # Get rit of the trailing Ls 381 | if sym_offset[:-1] == 'L': 382 | sym_offset = sym_offset[:-1] 383 | vals.append(sym_offset) 384 | init = ' = {{ {} }};'.format(', '.join(vals)) 385 | poc.append(line[:-1] + init) 386 | last_action_size += 1 387 | elif 'size_t fill_sizes' in line: 388 | vals = [] 389 | prev_part = (0, 0x0) 390 | for idx, var in enumerate(fsizes): 391 | if idx < len(fsizes) - 1: 392 | next_part = fsizes[idx + 1] 393 | else: 394 | next_part = 0x0 395 | sym_offset, prev_part = check_addr(var, next_part, prev_part, main_bin, heap_base, allocs, None, 0) 396 | # Get rit of the trailing Ls 397 | if sym_offset[:-1] == 'L': 398 | sym_offset = sym_offset[:-1] 399 | vals.append(sym_offset) 400 | init = ' = {{ {} }};'.format(', '.join(vals)) 401 | poc.append(line[:-1] + init) 402 | last_action_size += 1 403 | elif 'size_t overflow_sizes' in line: 404 | vals = [] 405 | prev_part = (0, 0x0) 406 | for idx, var in enumerate(osizes): 407 | if idx < len(osizes) - 1: 408 | next_part = osizes[idx + 1] 409 | else: 410 | next_part = 0x0 411 | sym_offset, prev_part = check_addr(var, next_part, prev_part, main_bin, heap_base, allocs, None, 0) 412 | # Get rit of the trailing Ls 413 | if sym_offset[:-1] == 'L': 414 | sym_offset = sym_offset[:-1] 415 | vals.append(sym_offset) 416 | init = ' = {{ {} }};'.format(', '.join(vals)) 417 | poc.append(line[:-1] + init) 418 | last_action_size += 1 419 | elif 'size_t header_size' in line: 420 | init = ' = 0x{:x};'.format(header) 421 | poc.append(line[:-1] + init) 422 | last_action_size += 1 423 | elif 'size_t mem2chunk_offset' in line: 424 | if result['mem2chunk_offset']: 425 | init = ' = 0x{:x};'.format(result['mem2chunk_offset']) 426 | poc.append(line[:-1] + init) 427 | else: 428 | poc.append(line) 429 | last_action_size += 1 430 | elif 'size_t arw_offsets' in line: 431 | if not arw_offsets: 432 | poc.append(line) 433 | else: 434 | vals = [] 435 | prev_part = (0, 0x0) 436 | for idx, var in enumerate(arw_offsets): 437 | if idx < len(arw_offsets) - 1: 438 | next_part = arw_offsets[idx + 1] 439 | else: 440 | next_part = 0x0 441 | sym_offset, prev_part = check_addr(var, next_part, prev_part, main_bin, heap_base, allocs, 442 | 'arw_offsets', idx * 8) 443 | # Get rit of the trailing Ls 444 | if sym_offset[:-1] == 'L': 445 | sym_offset = sym_offset[:-1] 446 | vals.append(sym_offset) 447 | init = ' = {{ {} }};'.format(', '.join(vals)) 448 | poc.append(line[:-1] + init) 449 | last_action_size += 1 450 | elif 'size_t bf_offsets' in line: 451 | if not bf_offsets: 452 | poc.append(line) 453 | else: 454 | vals = [] 455 | prev_part = (0, 0x0) 456 | for idx, var in enumerate(bf_offsets): 457 | if idx < len(bf_offsets) - 1: 458 | next_part = bf_offsets[idx + 1] 459 | else: 460 | next_part = 0x0 461 | sym_offset, prev_part = check_addr(var, next_part, prev_part, main_bin, heap_base, allocs, 462 | 'bf_offsets', idx * 8) 463 | # Get rit of the trailing Ls 464 | if sym_offset[:-1] == 'L': 465 | sym_offset = sym_offset[:-1] 466 | vals.append(sym_offset) 467 | init = ' = {{ {} }};'.format(', '.join(vals)) 468 | poc.append(line[:-1] + init) 469 | last_action_size += 1 470 | elif 'free(((uint8_t *) &sym_data.data) + mem2chunk_offset);' in line: # fake free 471 | # fake_free 472 | poc_desc['fake_frees'] += 1 473 | prev_part = (0, 0x0) 474 | for idx, var in enumerate(svars): 475 | if idx < len(svars) - 1: 476 | next_part = svars[idx + 1] 477 | else: 478 | next_part = 0x0 479 | sym_offset, prev_part = check_addr(var, next_part, prev_part, main_bin, heap_base, allocs, 480 | 'sym_data', 481 | idx * 8) 482 | # Get rit of the trailing Ls 483 | if sym_offset[-1] == 'L': 484 | sym_offset = sym_offset[:-1] 485 | poc.append('\t((uint64_t *) sym_data.data)[{}] = (uint64_t) {};'.format(idx, sym_offset)) 486 | last_action_size += 1 487 | poc.append(line) 488 | last_action_size += 1 489 | else: 490 | poc.append(line) 491 | last_action_size += 1 492 | 493 | # setup write_target 494 | vals = [] 495 | prev_part = (0, 0x0) 496 | for idx, var in enumerate(wtargets): 497 | if idx < len(wtargets) - 1: 498 | next_part = wtargets[idx + 1] 499 | else: 500 | next_part = 0x0 501 | sym_offset, prev_part = check_addr(var, next_part, prev_part, main_bin, heap_base, allocs, 'write_target', 502 | idx * 8) 503 | # Get rit of the trailing Ls 504 | if sym_offset[-1] == 'L': 505 | sym_offset = sym_offset[:-1] 506 | 507 | if not sym_offset == '0x0': 508 | poc_desc['constrained_target'] = True 509 | vals.append(sym_offset) 510 | content = [] 511 | for idx, val in enumerate(vals): 512 | content.append('\twrite_target[{}] = (uint64_t) {};'.format(idx, val)) 513 | 514 | if 0 == last_action_size: 515 | last_action_size = 1 516 | poc.insert(-last_action_size, '\n'.join(content)) 517 | poc.insert(-last_action_size, '{}#if print\n{}\tfor (int i = 0; i < 4; i++) {{'.format(space, space)) 518 | poc.insert(-last_action_size, 519 | '{}\t\tprintf("write_target[%d]: %p\\n", i, (void *) write_target[i]);'.format(space)) 520 | poc.insert(-last_action_size, '{}\t}}\n{}#endif\n'.format(space, space)) 521 | poc.append('{}#if print\n{}\tfor (int i = 0; i < 4; i++) {{'.format(space, space)) 522 | poc.append('{}\t\tprintf("write_target[%d]: %p\\n", i, (void *) write_target[i]);'.format(space)) 523 | poc.append('{}\t}}\n{}#endif\n'.format(space, space)) 524 | 525 | if result['vuln_type'].startswith('bad_allocation'): 526 | ptr = poc[-2].split(' ')[0].strip() 527 | poc.append('\t#if print\n\t\tprintf("Overlapping Allocation: %p\\n", {});\n\t#endif\n'.format(ptr)) 528 | 529 | # end main 530 | poc.append('}') 531 | pocs.append('\n'.join(poc)) 532 | poc_descs.append(poc_desc) 533 | 534 | # poc_desc are equal just return the first one 535 | return pocs, poc_descs[0] 536 | 537 | 538 | def create_makefile(poc_dir, fnames, allocator, libc): 539 | alloc_name = re.search(r'lib([\w]+).so', basename(allocator)).group(1) 540 | libc_name = re.search(r'lib([\w]+).so', basename(libc)).group(1) 541 | alloc_path = abspath(allocator) 542 | libc_path = abspath(libc) 543 | 544 | with open('{}/Makefile'.format(poc_dir), 'w') as f: 545 | f.write('CC = gcc\n') 546 | f.write('CFLAGS += -std=c99 -g -O0 -w\n') 547 | f.write('LDFLAGS += -no-pie\n') 548 | f.write('SOURCES = $(wildcard *.c)\n') 549 | f.write('OBJECTS = $(SOURCES:.c=.o)\n') 550 | f.write('BINARIES = {}\n'.format(' '.join(fnames))) 551 | f.write('DIRNAME = bin\n\n') 552 | f.write('PRINT = 0\n') 553 | f.write('.PHONY: all clean distclean gendir\n\n') 554 | f.write('all: pocs\n\n') 555 | f.write('clean:\n\trm $(OBJECTS)\n\n') 556 | f.write('distclean: clean\n\trm -r $(DIRNAME)\n\n') 557 | f.write('gendir:\n\tmkdir -p $(DIRNAME)\n\n') 558 | f.write('pocs: gendir $(BINARIES)\n') 559 | f.write('pocs-print: PRINT = 1\n') 560 | f.write('pocs-print: gendir $(BINARIES)\n\n') 561 | for file in fnames: 562 | f.write( 563 | '{}: {}.c\n\t$(CC) $(CFLAGS) -Dprint=$(PRINT) -o "$(DIRNAME)/$@.bin" -L{} -L{} $^ $(LDFLAGS) -l{} -l{}\n\n'.format( 564 | file, file, dirname(alloc_path), dirname(libc_path), alloc_name, libc_name)) 565 | 566 | 567 | def gen_pocs(config_file, bin_file, res_file, desc_file, src_file): 568 | config = parse_config(config_file) 569 | desc_dict = load_yaml(desc_file) 570 | results = load_yaml(res_file) 571 | 572 | last_lines = [] 573 | for result in results: 574 | last_lines.append((result['last_line'], get_last_line(result['last_line'], src_file))) 575 | 576 | poc_path = os.path.abspath(os.path.join(os.path.dirname(config_file.name), config['pocs_path'])) 577 | dir_path = gen_dir(poc_path, bin_file, results[0]['vuln_type'], results[0]['stack_trace']) 578 | 579 | logger.info('Found {} vulnerable paths'.format(len(results))) 580 | poc_descs = [] 581 | fnames = [] 582 | for i, result in enumerate(results): 583 | pocs, poc_desc = gen_poc(result, src_file, bin_file, last_lines[i][0]) 584 | poc_descs.append(poc_desc) 585 | logger.info('Generated {} poc(s) out of path {}'.format(len(pocs), i)) 586 | for i, poc in enumerate(pocs): 587 | fname = 'poc_{}_{}'.format(result['path_id'], i) 588 | with open('{}/{}.c'.format(dir_path, fname), 'w') as f: 589 | f.write(poc) 590 | fnames.append(fname) 591 | 592 | create_makefile(dir_path, fnames, config['allocator'], config['libc']) 593 | 594 | for i, desc in enumerate(desc_dict['text']): 595 | desc += '\n\t- Line {}: {}'.format(last_lines[i][0], last_lines[i][1].strip(' \t')) 596 | with open('{}/poc_{}.desc'.format(dir_path, i), 'w') as f: 597 | f.write(desc) 598 | 599 | for i, poc_desc in enumerate(poc_descs): 600 | with open('{}/poc_{}.yaml'.format(dir_path, i), 'w') as f: 601 | yaml.dump(poc_desc, f) 602 | -------------------------------------------------------------------------------- /heaphopper/analysis/tracer/tracer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | 4 | import ana 5 | import math 6 | import re 7 | 8 | import cle 9 | 10 | import angr 11 | import claripy 12 | import os 13 | import yaml 14 | import sys 15 | from elftools.elf.elffile import ELFFile 16 | 17 | from angr.storage.file import Flags, SimFile 18 | from angr import SimHeapBrk 19 | from ..heap_condition_tracker import HeapConditionTracker, MallocInspect, FreeInspect 20 | from ..mem_limiter import MemLimiter 21 | from ..vuln_checker import VulnChecker 22 | from ..concretizer import Concretizer 23 | from ...utils.angr_tools import heardEnter, all_bytes 24 | from ...utils.parse_config import parse_config 25 | from ...utils.input import constrain_input 26 | 27 | logger = logging.getLogger('heap-tracer') 28 | 29 | # global vars 30 | DESC_HEADER = 'Vuln description for binary' 31 | DESC_SECTION_LINE = '==========================================' 32 | MALLOC_LIST = [] 33 | 34 | ''' 35 | cle issue related workarounds 36 | ''' 37 | 38 | 39 | def fix_loader_problem(proj, state, loader_name, version): 40 | loader_bin = proj.loader.shared_objects[loader_name] 41 | rtld_global = loader_bin.get_symbol('_rtld_global').rebased_addr 42 | 43 | # magic_offset = 0x227160 44 | # magic_offset = 0xef0 45 | 46 | if version == '2.27': 47 | where = rtld_global + 0xf00 48 | magic_offset = 0x10e0 49 | what = loader_bin.mapped_base + magic_offset 50 | else: 51 | raise Exception("Don't know how to fix loader problem for libc-{}".format(version)) 52 | 53 | state.memory.store(where, what, endness="Iend_LE") 54 | 55 | 56 | ''' 57 | Exploration helper functions 58 | ''' 59 | 60 | 61 | def use_sim_procedure(name): 62 | if name in ['puts', 'printf', '__libc_start_main']: 63 | return False 64 | else: 65 | return True 66 | 67 | 68 | def ana_setup(): 69 | ana.set_dl(ana.DictDataLayer()) 70 | 71 | 72 | def ana_teardown(): 73 | ana.set_dl(ana.SimpleDataLayer()) 74 | 75 | 76 | def priority_key(state): 77 | return hash(tuple(state.history.bbl_addrs)) 78 | 79 | 80 | ''' 81 | Map addrs to source code using DWARF info 82 | ''' 83 | 84 | 85 | def decode_file_line(dwarfinfo, address): 86 | # Go over all the line programs in the DWARF information, looking for 87 | # one that describes the given address. 88 | for CU in dwarfinfo.iter_CUs(): 89 | # First, look at line programs to find the file/line for the address 90 | lineprog = dwarfinfo.line_program_for_CU(CU) 91 | prevstate = None 92 | for entry in lineprog.get_entries(): 93 | # We're interested in those entries where a new state is assigned 94 | if entry.state is None or entry.state.end_sequence: 95 | continue 96 | # Looking for a range of addresses in two consecutive states that 97 | # contain the required address. 98 | if prevstate and prevstate.address <= address < entry.state.address: 99 | filename = lineprog['file_entry'][prevstate.file - 1].name 100 | line = prevstate.line 101 | return filename, line 102 | prevstate = entry.state 103 | return None, None 104 | 105 | 106 | def get_last_line(win_addr, bin_file): 107 | with open(bin_file, 'rb') as bf: 108 | elffile = ELFFile(bf) 109 | 110 | if not elffile.has_dwarf_info(): 111 | print('{} has no DWARF info'.format(bin_file)) 112 | sys.exit(1) 113 | 114 | # get_dwarf_info returns a DWARFInfo context object, which is the 115 | # starting point for all DWARF-based processing in pyelftools. 116 | dwarfinfo = elffile.get_dwarf_info() 117 | 118 | file_name, line = decode_file_line(dwarfinfo, win_addr) 119 | 120 | return line 121 | 122 | 123 | def get_win_addr(proj, addr_trace): 124 | heap_func = None 125 | addr_trace = addr_trace[:-1] # discard last element 126 | for addr in addr_trace[::-1]: 127 | if proj.loader.main_object.contains_addr(addr): 128 | if heap_func: 129 | return addr, heap_func 130 | else: # heap-func in plt first 131 | heap_func = proj.loader.main_object.reverse_plt[addr] 132 | elif heap_func: 133 | # last main_object addr has to be right before heap_func call 134 | raise Exception("Couldn't find win addr") 135 | 136 | 137 | ''' 138 | Post processing states 139 | ''' 140 | 141 | 142 | def process_state(num_results, state, write_state, var_dict, fd): 143 | processed_states = [] 144 | input_opts = state.solver.eval_upto(all_bytes(state.posix.fd[fd].read_storage), num_results, cast_to=bytes) 145 | for input_opt in input_opts: 146 | s = state.copy() 147 | write_s = write_state.copy() 148 | s.add_constraints(all_bytes(s.posix.fd[fd].read_storage) == input_opt) 149 | write_s.add_constraints(all_bytes(write_s.posix.fd[fd].read_storage) == input_opt) 150 | 151 | stdin_bytes = all_bytes(state.posix.fd[0].read_storage) 152 | if stdin_bytes: 153 | stdin_bytes = [b[0] for b in stdin_bytes] 154 | stdin_stream = stdin_bytes[0] 155 | for b in stdin_bytes[1:]: 156 | stdin_stream = stdin_stream.concat(b) 157 | stdin_opt = state.solver.eval(stdin_stream, cast_to=bytes) 158 | s.add_constraints(stdin_stream == stdin_opt) 159 | write_s.add_constraints(stdin_stream == stdin_opt) 160 | else: 161 | stdin_opt = [] 162 | 163 | svars = [] 164 | # check if we have a saved-state for the sym_data memory 165 | if 'sym_data_ptr' in var_dict and s.heaphopper.sym_data_states[var_dict['sym_data_ptr']]: 166 | sym_s = s.heaphopper.sym_data_states[var_dict['sym_data_ptr']].copy() 167 | sym_s.add_constraints(all_bytes(sym_s.posix.fd[fd].read_storage) == input_opt) 168 | if stdin_opt: 169 | sym_s.add_constraints(stdin_stream == stdin_opt) 170 | else: 171 | sym_s = s 172 | for svar in var_dict['sdata_addrs']: 173 | smem_orig = sym_s.memory.load(svar, 8, endness='Iend_LE') 174 | smem_final = s.memory.load(svar, 8, endness='Iend_LE') 175 | # If the symbolic variable was overwritten, use the orig one 176 | if smem_orig is not smem_final: 177 | sol = sym_s.solver.min(smem_orig) 178 | sym_s.add_constraints(smem_orig == sol) 179 | else: 180 | sol = s.solver.min(smem_final) 181 | s.add_constraints(smem_final == sol) 182 | svars.append(sol) 183 | 184 | header_var = s.memory.load(var_dict['header_size_addr'], 8, endness="Iend_LE") 185 | header = s.solver.min(header_var) 186 | s.add_constraints(header_var == header) 187 | write_s.add_constraints(header_var == header) 188 | 189 | msizes = [] 190 | for malloc_size in var_dict['malloc_size_addrs']: 191 | size_var = s.memory.load(malloc_size, 8, endness="Iend_LE") 192 | sol = s.solver.min(size_var) 193 | msizes.append(sol) 194 | s.add_constraints(size_var == sol) 195 | write_s.add_constraints(size_var == sol) 196 | 197 | fsizes = [] 198 | for fill_size in var_dict['fill_size_vars']: 199 | sol = s.solver.min(fill_size) 200 | fsizes.append(sol) 201 | s.add_constraints(fill_size == sol) 202 | write_s.add_constraints(fill_size == sol) 203 | 204 | osizes = [] 205 | for overflow_size in var_dict['overflow_sizes']: 206 | sol = s.solver.min(overflow_size) 207 | osizes.append(sol) 208 | s.add_constraints(overflow_size == sol) 209 | write_s.add_constraints(overflow_size == sol) 210 | 211 | wt_mem = [] 212 | for write_target in var_dict['wtarget_addrs']: 213 | wt_var = write_s.memory.load(write_target, 8, endness="Iend_LE") 214 | sol = write_s.solver.min(wt_var) 215 | wt_mem.append(sol) 216 | write_s.add_constraints(wt_var == sol) 217 | 218 | allocs = [] 219 | for allocation in var_dict['allocs']: 220 | alloc_var = s.memory.load(allocation, 8, endness="Iend_LE") 221 | sol = s.solver.min(alloc_var) 222 | allocs.append(sol) 223 | s.add_constraints(alloc_var == sol) 224 | write_s.add_constraints(alloc_var == sol) 225 | 226 | arb_offsets = [] 227 | for arb_var in var_dict['arb_offset_vars']: 228 | arb_offset = s.memory.load(arb_var, 8, endness="Iend_LE") 229 | sol = s.solver.min(arb_offset) 230 | arb_offsets.append(sol) 231 | s.add_constraints(arb_offset == sol) 232 | write_s.add_constraints(arb_offset == sol) 233 | 234 | bf_offsets = [] 235 | for bf_var in var_dict['bf_offset_vars']: 236 | bf_offset = s.memory.load(bf_var, 8, endness="Iend_LE") 237 | sol = s.solver.min(bf_offset) 238 | bf_offsets.append(sol) 239 | s.add_constraints(bf_offset == sol) 240 | write_s.add_constraints(bf_offset == sol) 241 | 242 | arb_write = {} 243 | if s.heaphopper.arb_write_info: 244 | arb_write = {k: s.solver.eval(v) for k, v in list(s.heaphopper.arb_write_info.items())} 245 | 246 | processed_states.append( 247 | (input_opt, stdin_opt, svars, header, msizes, fsizes, osizes, wt_mem, allocs, arb_offsets, 248 | bf_offsets, arb_write)) 249 | return processed_states 250 | 251 | 252 | ''' 253 | Configure state and fill memory with symbolic variables 254 | ''' 255 | 256 | 257 | def setup_state(state, proj, config): 258 | 259 | # Inject symbolic controlled data into memory 260 | var_dict = dict() 261 | var_dict['global_vars'] = [] 262 | var_dict['allocs'] = [] 263 | for i in range(20): 264 | cdata = proj.loader.main_object.get_symbol('ctrl_data_{}'.format(i)) 265 | # check if ctrl_data exists 266 | if not cdata: 267 | break 268 | 269 | var_dict['global_vars'].append(cdata.rebased_addr) 270 | var_dict['allocs'].append(cdata.rebased_addr) 271 | 272 | # Set mem2chunk offset 273 | mem2chunk_var = proj.loader.main_object.get_symbol('mem2chunk_offset') 274 | var_dict['mem2chunk_addr'] = mem2chunk_var.rebased_addr 275 | mem2chunk = state.solver.BVV(value=config['mem2chunk_offset'], size=8 * 8) 276 | state.memory.store(var_dict['mem2chunk_addr'], mem2chunk, 8, endness='Iend_LE') 277 | 278 | # Inject symbolic data into memory that can't be resolved 279 | var_dict['sdata_addrs'] = [] 280 | sdata_var = proj.loader.main_object.get_symbol('sym_data') 281 | # check if sym_data exists 282 | if sdata_var: 283 | for i in range(0, config['sym_data_size'], 8): 284 | smem_elem = state.solver.BVS('smem', 8 * 8) 285 | state.memory.store(sdata_var.rebased_addr + i, smem_elem, 8, endness='Iend_LE') 286 | var_dict['sdata_addrs'].append(sdata_var.rebased_addr + i) 287 | 288 | # create entry in sym_data state storage 289 | var_dict['sym_data_ptr'] = sdata_var.rebased_addr + config['mem2chunk_offset'] 290 | state.heaphopper.sym_data_states[var_dict['sym_data_ptr']] = None 291 | # add sym_data_size to heap state 292 | state.heaphopper.sym_data_size = config['sym_data_size'] 293 | # add global_ptr to global_vars: 294 | var_dict['global_vars'].append(sdata_var.rebased_addr) 295 | 296 | # Setup write_target 297 | var_dict['wtarget_addrs'] = [] 298 | write_target_var = proj.loader.main_object.get_symbol('write_target') 299 | for i in range(0, write_target_var.size, 8): 300 | write_mem_elem = state.solver.BVS('write_mem', 8 * 8) 301 | state.memory.store(write_target_var.rebased_addr + i, write_mem_elem, 8, endness='Iend_LE') 302 | var_dict['wtarget_addrs'].append(write_target_var.rebased_addr + i) 303 | var_dict['global_vars'].append(write_target_var.rebased_addr + i) 304 | 305 | # Set header size 306 | header_size_var = proj.loader.main_object.get_symbol('header_size') 307 | var_dict['header_size_addr'] = header_size_var.rebased_addr 308 | header_size = state.solver.BVV(value=config['header_size'], size=8 * 8) 309 | state.memory.store(var_dict['header_size_addr'], header_size, 8, endness='Iend_LE') 310 | 311 | 312 | # Set malloc sizes 313 | malloc_size_var = proj.loader.main_object.get_symbol('malloc_sizes') 314 | var_dict['malloc_size_addrs'] = [malloc_size_var.rebased_addr + i for i in range(0, malloc_size_var.size, 8)] 315 | var_dict['malloc_size_bvs'] = [] 316 | 317 | if max(config['malloc_sizes']) != 0: 318 | bvs_size = int(math.ceil(math.log(max(config['malloc_sizes']), 2))) + 1 319 | else: 320 | bvs_size = 8 321 | num_bytes = int(math.ceil(bvs_size / float(state.arch.byte_width))) 322 | bit_diff = num_bytes * state.arch.byte_width - bvs_size 323 | 324 | for msize in var_dict['malloc_size_addrs']: 325 | if len(config['malloc_sizes']) > 1: 326 | malloc_var = state.solver.BVS('malloc_size', bvs_size).zero_extend(bit_diff) 327 | constraint = claripy.Or(malloc_var == config['malloc_sizes'][0]) 328 | for bin_size in config['malloc_sizes'][1:]: 329 | constraint = claripy.Or(malloc_var == bin_size, constraint) 330 | state.add_constraints(constraint) 331 | else: 332 | malloc_var = state.solver.BVV(config['malloc_sizes'][0], state.arch.bits) 333 | var_dict['malloc_size_bvs'].append(malloc_var) 334 | state.memory.store(msize, claripy.BVV(0, 8 * 8), endness='Iend_LE') # zero-fill first just in case 335 | state.memory.store(msize, malloc_var, endness='Iend_LE') 336 | 337 | # Set fill sizes 338 | fill_size_var = proj.loader.main_object.get_symbol('fill_sizes') 339 | var_dict['fill_size_addrs'] = [fill_size_var.rebased_addr + i for i in range(0, fill_size_var.size, 8)] 340 | var_dict['fill_size_vars'] = [] 341 | if config['chunk_fill_size'] == 'zero': 342 | var_dict['fill_size_vars'] = [state.solver.BVV(0, 8 * 8)] * len(var_dict['fill_size_addrs']) 343 | if config['chunk_fill_size'] == 'header_size': 344 | var_dict['fill_size_vars'] = [header_size] * len(var_dict['fill_size_addrs']) 345 | if config['chunk_fill_size'] == 'chunk_size': 346 | var_dict['fill_size_vars'] = var_dict['malloc_size_bvs'] 347 | if type(config['chunk_fill_size']) in (int, int): 348 | var_dict['fill_size_vars'] = [claripy.BVV(config['chunk_fill_size'], 8 * 8)] * len(var_dict['fill_size_addrs']) 349 | 350 | for fsize, fill_var in zip(var_dict['fill_size_addrs'], var_dict['fill_size_vars']): 351 | state.memory.store(fsize, claripy.BVV(0, 8 * 8), endness='Iend_LE') # zero-fill first just in case 352 | state.memory.store(fsize, fill_var, endness='Iend_LE') 353 | 354 | # Set overflow sizes 355 | overflow_size_var = proj.loader.main_object.get_symbol('overflow_sizes') 356 | overflow_size_offset = overflow_size_var.rebased_addr 357 | var_dict['overflow_sizes_addrs'] = [overflow_size_offset + i for i in range(0, overflow_size_var.size, 8)] 358 | 359 | if max(config['overflow_sizes']) != 0: 360 | bvs_size = int(math.ceil(math.log(max(config['overflow_sizes']), 2))) + 1 361 | else: 362 | bvs_size = 8 363 | num_bytes = int(math.ceil(bvs_size / float(state.arch.byte_width))) 364 | bit_diff = num_bytes * state.arch.byte_width - bvs_size 365 | 366 | var_dict['overflow_sizes'] = [] 367 | for overflow_size_addr in var_dict['overflow_sizes_addrs']: 368 | if len(config['overflow_sizes']) > 1: 369 | overflow_var = state.solver.BVS('overflow_size', bvs_size).zero_extend(bit_diff) 370 | constraint = claripy.Or(overflow_var == config['overflow_sizes'][0]) 371 | for bin_size in config['overflow_sizes'][1:]: 372 | constraint = claripy.Or(overflow_var == bin_size, constraint) 373 | state.add_constraints(constraint) 374 | else: 375 | overflow_var = state.solver.BVV(config['overflow_sizes'][0], state.arch.bits) 376 | var_dict['overflow_sizes'].append(overflow_var) 377 | 378 | state.memory.store(overflow_size_addr, overflow_var, endness='Iend_LE') 379 | 380 | # Get arb_write_offsets 381 | var_dict['arb_offset_vars'] = [] 382 | arb_write_var = proj.loader.main_object.get_symbol('arw_offsets') 383 | for i in range(0, arb_write_var.size, 8): 384 | var_dict['arb_offset_vars'].append(arb_write_var.rebased_addr + i) 385 | 386 | # Get bf_offsets 387 | var_dict['bf_offset_vars'] = [] 388 | bf_var = proj.loader.main_object.get_symbol('bf_offsets') 389 | for i in range(0, bf_var.size, 8): 390 | var_dict['bf_offset_vars'].append(bf_var.rebased_addr + i) 391 | 392 | # get winning addr 393 | var_dict['winning_addr'] = proj.loader.main_object.get_symbol('winning').rebased_addr 394 | 395 | return var_dict 396 | 397 | 398 | ''' 399 | Main trace function 400 | ''' 401 | 402 | 403 | def trace(config_name, binary_name): 404 | # Get config 405 | config = parse_config(config_name) 406 | 407 | # Set logging 408 | logger.info('Searching for vulns') 409 | logging.basicConfig() 410 | # TODO: disable in production 411 | angr.manager.l.setLevel(config['log_level']) 412 | angr.exploration_techniques.spiller.l.setLevel(config['log_level']) 413 | cle.loader.log.setLevel(config['log_level']) 414 | logger.setLevel(config['log_level']) 415 | 416 | # Get libc path 417 | libc_path = os.path.expanduser(config['libc']) 418 | libc_name = os.path.basename(libc_path) 419 | # Get allocator path 420 | allocator_path = os.path.expanduser(config['allocator']) 421 | if allocator_path == 'default': 422 | allocator_path = libc_path 423 | 424 | allocator_name = os.path.basename(allocator_path) 425 | 426 | # Create project and disable sim_procedures for the libc 427 | proj = angr.Project(binary_name, exclude_sim_procedures_func=use_sim_procedure, 428 | load_options={'ld_path':[os.path.dirname(allocator_path), os.path.dirname(libc_path)], 429 | 'auto_load_libs':True}) 430 | 431 | # Find write_target 432 | write_target_var = proj.loader.main_object.get_symbol('write_target') 433 | 434 | # Get libc 435 | libc = proj.loader.shared_objects[libc_name] 436 | 437 | # Get allocator 438 | allocator = proj.loader.shared_objects[allocator_name] 439 | 440 | # Create state and enable reverse memory map 441 | added_options = set() 442 | added_options.add(angr.options.REVERSE_MEMORY_NAME_MAP) 443 | added_options.add(angr.options.STRICT_PAGE_ACCESS) 444 | added_options.add(angr.options.CONCRETIZE_SYMBOLIC_FILE_READ_SIZES) 445 | added_options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY) 446 | added_options.add(angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS) 447 | 448 | added_options.add(angr.options.SIMPLIFY_EXPRS) 449 | added_options.update(angr.options.simplification) 450 | added_options.update(angr.options.unicorn) 451 | 452 | if config['use_vsa']: 453 | added_options.add(angr.options.APPROXIMATE_SATISFIABILITY) # vsa for satisfiability 454 | added_options.add(angr.options.APPROXIMATE_GUARDS) # vsa for guards 455 | added_options.add(angr.options.APPROXIMATE_MEMORY_SIZES) # vsa for mem_sizes 456 | added_options.add(angr.options.APPROXIMATE_MEMORY_INDICES) # vsa for mem_indices 457 | 458 | removed_options = set() 459 | removed_options.add(angr.options.LAZY_SOLVES) 460 | 461 | # set heap location right after bss 462 | bss = proj.loader.main_object.sections_map['.bss'] 463 | heap_base = ((bss.vaddr + bss.memsize) & ~0xfff) + 0x1000 464 | heap_size = 64 * 4096 465 | new_brk = claripy.BVV(heap_base + heap_size, proj.arch.bits) 466 | 467 | 468 | state = proj.factory.entry_state(add_options=added_options, remove_options=removed_options) 469 | # heap = SimHeapBrk(heap_base=heap_base, heap_size=heap_size) 470 | #state.register_plugin('heap', heap) 471 | state.register_plugin('heaphopper', HeapConditionTracker(config=config, 472 | wtarget=(write_target_var.rebased_addr, 473 | write_target_var.size), 474 | libc=libc, allocator=allocator)) 475 | 476 | # state.posix.set_brk(new_brk) # now handled in the heap plugin 477 | state.posix.brk = heap_base + heap_size 478 | state.heap.heap_base = heap_base 479 | state.heap.mmap_base = heap_base + 2*heap_size 480 | state.heaphopper.set_level(config['log_level']) 481 | 482 | if config['fix_loader_problem']: 483 | loader_name = os.path.basename(config['loader']) 484 | libc_version = re.findall(r'libc-([\d\.]+)', libc_path)[0] 485 | fix_loader_problem(proj, state, loader_name, libc_version) 486 | 487 | var_dict = setup_state(state, proj, config) 488 | 489 | # Set memory concretization strategies 490 | state.memory.read_strategies = [ 491 | angr.concretization_strategies.SimConcretizationStrategySolutions(16), 492 | angr.concretization_strategies.SimConcretizationStrategyControlledData(4096, 493 | var_dict[ 494 | 'global_vars']), 495 | angr.concretization_strategies.SimConcretizationStrategyEval(4096)] 496 | state.memory.write_strategies = [ 497 | angr.concretization_strategies.SimConcretizationStrategySolutions(16), 498 | angr.concretization_strategies.SimConcretizationStrategyControlledData(4096, 499 | var_dict[ 500 | 'global_vars']), 501 | angr.concretization_strategies.SimConcretizationStrategyEval(4096)] 502 | 503 | # Hook malloc and free 504 | malloc_addr = allocator.get_symbol('malloc').rebased_addr 505 | free_addr = allocator.get_symbol('free').rebased_addr 506 | malloc_plt = proj.loader.main_object.plt.get('malloc') 507 | free_plt = proj.loader.main_object.plt.get('free') 508 | proj.hook(addr=malloc_plt, 509 | hook=MallocInspect(malloc_addr=malloc_addr, vulns=config['vulns'], ctrl_data=var_dict['allocs'])) 510 | proj.hook(addr=free_plt, 511 | hook=FreeInspect(free_addr=free_addr, vulns=config['vulns'], sym_data=var_dict['sdata_addrs'])) 512 | 513 | found_paths = [] 514 | 515 | # Configure simgr 516 | #sm = proj.factory.simgr(thing=state, immutable=False) 517 | sm = proj.factory.simgr(thing=state) 518 | 519 | sm.use_technique(VulnChecker(config['mem_corruption_fd'], config['input_pre_constraint'], config['input_values'], 520 | config['stop_found'], config['filter_fake_frees'])) 521 | 522 | if config['use_mem_limiter']: 523 | sm.use_technique(MemLimiter(config['mem_limit'], config['drop_errored'])) 524 | if config['use_dfs']: 525 | sm.use_technique(angr.exploration_techniques.DFS()) 526 | if config['use_veritesting']: 527 | sm.use_technique(angr.exploration_techniques.Veritesting()) 528 | if config['use_concretizer']: 529 | concr_addrs = var_dict['malloc_size_addrs'] + var_dict['allocs'] 530 | sm.use_technique(Concretizer(concr_addrs)) 531 | if config['spiller']: 532 | if config['dfs']: 533 | src_stash = 'deferred' 534 | else: 535 | src_stash = 'active' 536 | spill_conf = config['spiller_conf'] 537 | ana_setup() 538 | spiller = angr.exploration_techniques.Spiller( 539 | src_stash=src_stash, min=spill_conf['min'], max=spill_conf['max'], 540 | staging_min=spill_conf['staging_min'], staging_max=spill_conf['staging_max'], 541 | priority_key=priority_key 542 | ) 543 | sm.use_technique(spiller) 544 | 545 | avoids = (libc.get_symbol('abort').rebased_addr,) 546 | sm.use_technique(angr.exploration_techniques.Explorer(find=(var_dict['winning_addr'],), avoid=avoids)) 547 | 548 | # Create fd for memory corruption input 549 | name = b'memory_corruption' 550 | path = b'/tmp/%s' % name 551 | mem_corr_fd = config['mem_corruption_fd'] 552 | # backing = SimSymbolicMemory(memory_id='file_%s' % name) 553 | # backing.set_state(state) 554 | f = SimFile(name, writable=False) 555 | f.set_state(state) 556 | state.fs.insert(path, f) 557 | real_fd = state.posix.open(path, flags=Flags.O_RDONLY, preferred_fd=mem_corr_fd) 558 | if mem_corr_fd != real_fd: 559 | raise Exception("Overflow fd already exists.") 560 | #state.posix.fd[mem_corr_fd] = f 561 | #state.posix.fs[name] = f 562 | 563 | # constrain input 564 | if config['input_pre_constraint']: 565 | if 'overflow' in config['zoo_actions']: 566 | overflow_bytes = config['zoo_actions']['overflow'] * max(config['overflow_sizes']) 567 | else: 568 | overflow_bytes = 0 569 | 570 | if 'uaf' in config['zoo_actions']: 571 | uaf_bytes = config['zoo_actions']['uaf'] * config['header_size'] 572 | else: 573 | uaf_bytes = 0 574 | 575 | if 'arb_relative_write' in config['zoo_actions']: 576 | arw_bytes = config['zoo_actions']['arb_relative'] * state.arch.byte_width * 2 577 | else: 578 | arw_bytes = 0 579 | 580 | num_bytes = overflow_bytes + uaf_bytes + arw_bytes 581 | input_bytes = state.posix.fd[mem_corr_fd].read_data(num_bytes)[0].chop(8) 582 | state.posix.fd[mem_corr_fd].seek(0) 583 | constrain_input(state, input_bytes, config['input_values']) 584 | 585 | # Manual inspection loop 586 | debug = False 587 | stop = False 588 | while len(sm.active) > 0 and not stop: 589 | if debug: 590 | debug = False 591 | 592 | sm.step() 593 | 594 | if heardEnter(): 595 | debug = True 596 | 597 | sm.move(from_stash='found', to_stash='vuln', filter_func=lambda p: p.heaphopper.vulnerable) 598 | found_paths.extend(sm.vuln) 599 | 600 | if config['spiller']: 601 | ana_teardown() 602 | 603 | for path in found_paths: 604 | win_addr, heap_func = get_win_addr(proj, path.history.bbl_addrs.hardcopy) 605 | path.heaphopper.win_addr = win_addr 606 | last_line = get_last_line(win_addr, binary_name) 607 | path.heaphopper.last_line = last_line 608 | if path.heaphopper.arb_write_info: 609 | path.heaphopper.arb_write_info['instr'] = proj.loader.shared_objects[ 610 | allocator_name].addr_to_offset( 611 | path.heaphopper.arb_write_info['instr']) 612 | logger.info('Found {} vulns'.format(len(found_paths))) 613 | 614 | if len(found_paths) > 0: 615 | arb_writes = store_results(config['num_results'], binary_name, found_paths, var_dict, 616 | config['mem_corruption_fd']) 617 | if config['store_desc']: 618 | store_vuln_descs(binary_name, found_paths, var_dict, arb_writes) 619 | return 0 620 | 621 | 622 | def store_vuln_descs(desc_file, states, var_dict, arb_writes): 623 | global DESC_HEADER, DESC_SECTION_LINE 624 | logger.info('Creating vuln descriptions: {}-desc.yaml'.format(desc_file)) 625 | with open('{}.desc'.format(desc_file.split('.')[0]), 'r') as f: 626 | bin_info = yaml.load(f, Loader=yaml.SafeLoader) 627 | ''' 628 | bin_info['allocs'] -> list of (dst_symbol, size_symbol) 629 | bin_info['frees'] -> list of (dst_symbol) 630 | bin_info['reads'] -> list of (dst_symbol, size_symbol) 631 | bin_info['overflows'] -> list of (src_symbol, dst_symbol, size_symbol) 632 | bin_info['fake_frees'] -> list of fake_chunk_ptrs 633 | bin_info['double_frees'] -> list of double_frees 634 | bin_info['arb_relative_writes'] -> list of arb_relative_writes 635 | bin_info['single_bitflips'] -> list of single_bitflips 636 | bin_info['uafs'] -> list of use-after_frees 637 | ''' 638 | descs = [] 639 | for state_num, state in enumerate(states): 640 | desc = [] 641 | desc.append('{} {}'.format(DESC_HEADER, desc_file)) 642 | desc.append('CONSTRAINTS:') 643 | desc.append(DESC_SECTION_LINE) 644 | desc.append('\t- {} allocations:'.format(len(bin_info['allocs']))) 645 | # show mallocs: 646 | for msize, minfo in zip(var_dict['malloc_size_addrs'], bin_info['allocs']): 647 | size_val = state.solver.eval(state.memory.load(msize, 8, endness='Iend_LE')) 648 | desc.append('\t\t* {} byte allocation to {}'.format(size_val, minfo[0])) 649 | # show frees 650 | desc.append('\t- {} frees:'.format(len(bin_info['frees']))) 651 | for free in bin_info['frees']: 652 | desc.append('\t\t* free of {}'.format(free)) 653 | # show overflows 654 | desc.append('\t- {} overflows:'.format(len(bin_info['overflows']))) 655 | for of, of_size in zip(bin_info['overflows'], var_dict['overflow_sizes_addrs']): 656 | size_val = state.solver.min(state.memory.load(of_size, 8, endness='Iend_LE')) 657 | desc.append('\t\t* {} byte overflow from {} into {}'.format(size_val, of[0], of[1])) 658 | # show bad frees 659 | desc.append('\t- {} bad_frees:'.format(len(bin_info['fake_frees']))) 660 | for fake_free in bin_info['fake_frees']: 661 | desc.append('\t\t* free of fake_chunk {}'.format(fake_free)) 662 | if state.heaphopper.double_free: 663 | desc.append('\t- {} double free(s)'.format(len(state.heaphopper.double_free))) 664 | # show arbitrary relative writes 665 | desc.append('\t- {} arb_relative_writes:'.format(len(bin_info['arb_relative_writes']))) 666 | for dst, offset in bin_info['arb_relative_writes']: 667 | desc.append('\t\t* arbitrary relative write to {} at offset {}'.format(dst, offset)) 668 | # show single bitflips 669 | desc.append('\t- {} single_bitflips:'.format(len(bin_info['single_bitflips']))) 670 | for dst, offset, bit in bin_info['single_bitflips']: 671 | desc.append('\t\t* single bitflip to {} at offset {} on bit {}'.format(dst, offset, bit)) 672 | # show single bitflips 673 | desc.append('\t- {} uafs:'.format(len(bin_info['uafs']))) 674 | for dst, size in bin_info['uafs']: 675 | desc.append('\t\t* use-after-free of {} with size of {}'.format(dst, size)) 676 | # check controlled_data 677 | desc.append('\t- controlled_data:') 678 | stdin_bytes = all_bytes(state.posix.fd[0].read_storage) 679 | if stdin_bytes: 680 | stdin_bytes = [b[0] for b in stdin_bytes] 681 | stdin = stdin_bytes[0] 682 | for b in stdin_bytes[1:]: 683 | stdin = stdin.concat(b) 684 | constraint_vars = [list(c.variables) for c in state.solver.constraints] 685 | i = 0 686 | for read, fill_size in zip(bin_info['reads'], var_dict['fill_size_vars']): 687 | sol = state.solver.min(fill_size) 688 | for j in range(0, sol, 8): 689 | i += j 690 | # We should never end up here if stdin doesn't exist 691 | curr_input = stdin.reversed[i + 7:i] 692 | input_vars = list(curr_input.variables) 693 | for input_var in input_vars: 694 | if input_var in constraint_vars: 695 | desc.append('\t\t* 8 byte of controlled data in the heap @ {}+{}'.format(read[0], i)) 696 | 697 | desc.append('\t- symbolic_data:') 698 | for sdata in var_dict['sdata_addrs']: 699 | mem = state.memory.load(sdata, 8, endness='Iend_LE') 700 | mem_vars = list(mem.variables) 701 | for mem_var in mem_vars: 702 | if mem_var in constraint_vars: 703 | desc.append('\t\t* 8 byte of symbolic data in the bss @ {}'.format(sdata)) 704 | 705 | desc.append('\n\nRESULT:') 706 | desc.append(DESC_SECTION_LINE) 707 | if state.heaphopper.vuln_type == 'malloc_non_heap': 708 | desc.append('\t- malloc returns a pointer to non-heap segment') 709 | if state.heaphopper.vuln_type == 'malloc_allocated': 710 | desc.append('\t- malloc returns a pointer to an already allocated heap region') 711 | if state.heaphopper.stack_trace: 712 | desc.append('\t- arbitrary write stack_trace:') 713 | for idx, addr in enumerate(state.heaphopper.stack_trace): 714 | desc.append('\t\t[{}] {}'.format(idx, hex(addr))) 715 | if state.heaphopper.vuln_type == 'arbitrary_write_malloc': 716 | desc.append('\t- arbitrary write in malloc') 717 | for idx, arb_write in enumerate(arb_writes[state_num]): 718 | desc.append( 719 | '\t\t{0:d}: Instr: 0x{1:x}; TargetAddr: 0x{2:x}; Val: 0x{3:x}'.format(idx, 720 | arb_write['instr'] & 0xffffff, 721 | arb_write['addr'], 722 | arb_write['val'])) 723 | if state.heaphopper.vuln_type == 'arbitrary_write_free': 724 | desc.append('\t- arbitrary write in free:') 725 | for idx, arb_write in enumerate(arb_writes[state_num]): 726 | desc.append( 727 | '\t\t{0:d}: Instr: 0x{1:x}; TargetAddr: 0x{2:x}; Val: 0x{3:x}'.format(idx, 728 | arb_write['instr'] & 0xffffff, 729 | arb_write['addr'], 730 | arb_write['val'])) 731 | descs.append('\n'.join(desc)) 732 | 733 | desc_dict = {'file': desc_file, 'text': descs} 734 | with open("{}-desc.yaml".format(desc_file), 'w') as f: 735 | yaml.dump(desc_dict, f) 736 | 737 | 738 | # Store results as yaml-file 739 | def store_results(num_results, bin_file, states, var_dict, fd): 740 | logger.info('Storing result infos to: {}-result.yaml'.format(bin_file)) 741 | results = [] 742 | arbitrary_writes = [] 743 | for i, state in enumerate(states): 744 | result = dict() 745 | result['file'] = bin_file 746 | result['path_id'] = i 747 | result['input_opts'] = [] 748 | result['stdin_opts'] = [] 749 | result['symbolic_data'] = [] 750 | result['malloc_sizes'] = [] 751 | result['fill_sizes'] = [] 752 | result['header_sizes'] = [] 753 | result['overflow_sizes'] = [] 754 | result['write_targets'] = [] 755 | result['mem2chunk_offset'] = state.solver.eval(state.memory.load(var_dict['mem2chunk_addr'], 8, 756 | endness='Iend_LE')) 757 | result['stack_trace'] = state.heaphopper.stack_trace 758 | result['last_line'] = state.heaphopper.last_line 759 | result['heap_base'] = state.heap.heap_base 760 | result['allocs'] = [] 761 | result['arb_write_offsets'] = [] 762 | result['bf_offsets'] = [] 763 | result['vuln_type'] = state.heaphopper.vuln_type 764 | 765 | arbitrary_write = [] 766 | 767 | processed_state = process_state(num_results, state, state.heaphopper.vuln_state, var_dict, fd) 768 | 769 | for input_opt, stdin_opt, svars, header, msizes, fsizes, osizes, wtargets, allocs, arb_offsets, bf_offsets, arb_write in processed_state: 770 | result['input_opts'].append(input_opt) 771 | result['stdin_opts'].append(stdin_opt) 772 | result['symbolic_data'].append(svars) 773 | result['header_sizes'].append(header) 774 | result['malloc_sizes'].append(msizes) 775 | result['fill_sizes'].append(fsizes) 776 | result['overflow_sizes'].append(osizes) 777 | result['write_targets'].append(wtargets) 778 | result['allocs'].append(allocs) 779 | result['arb_write_offsets'].append(arb_offsets) 780 | result['bf_offsets'].append(bf_offsets) 781 | arbitrary_write.append(arb_write) 782 | results.append(result) 783 | arbitrary_writes.append(arbitrary_write) 784 | 785 | with open("{}-result.yaml".format(bin_file), 'w') as f: 786 | yaml.dump(results, f) 787 | 788 | return arbitrary_writes 789 | --------------------------------------------------------------------------------