-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add parse_kconfig and kernel information to pwnlib.elf.ELF (#861)
- Loading branch information
1 parent
300f8e0
commit 8345bc3
Showing
4 changed files
with
361 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,5 @@ ELF Modules | |
|
||
.. toctree:: | ||
elf/elf | ||
elf/config | ||
elf/corefile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.. testsetup:: * | ||
|
||
from pwn import * | ||
from pwnlib.elf.config import parse_kconfig | ||
|
||
:mod:`pwnlib.elf.config` --- Kernel Config Parsing | ||
=========================================================== | ||
|
||
.. automodule:: pwnlib.elf.config | ||
:members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
""" | ||
Kernel-specific ELF functionality | ||
""" | ||
|
||
|
||
class KernelConfig(object): | ||
def __init__(self, name, title, requires=[], excludes=[], minver=0, maxver=99): | ||
|
||
#: Name of the configuration option | ||
self.name = name | ||
|
||
#: Section to which the configuration point belongs | ||
self.title = title | ||
|
||
#: List of configuration items, one of which must be present, | ||
#: for this checker to be used. | ||
self.requires = set(requires) | ||
|
||
#: List of configuration items, NONE of which must be present, | ||
#: for this checker to be used. | ||
self.excludes = set(excludes) | ||
|
||
#: Kernel version that this check should be enforced on | ||
self.minver = map(int, str(minver).split('.')) | ||
self.maxver = map(int, str(maxver).split('.')) | ||
|
||
def relevant(self, config): | ||
|
||
# If any of the excluded options are ENABLED, | ||
# this config is not relevant. | ||
if self.excludes: | ||
for value in self.excludes & set(config): | ||
if config.get(value, False): | ||
return False | ||
|
||
# If any of the required options are PRESENT but DISABLED, | ||
# this config is not relevant. | ||
if self.requires: | ||
for value in self.requires & set(config): | ||
if config.get(value, False): | ||
return True | ||
|
||
# We did not find a required value | ||
return False | ||
|
||
# If we are not in the correct version range, bail | ||
if 'version' in config: | ||
version = config['version'] | ||
|
||
if not (self.minver <= version < self.maxver): | ||
return False | ||
|
||
return True | ||
|
||
def check(self, value): | ||
raise NotImplementedError() | ||
|
||
def __call__(self, config): | ||
"""__call__(config) -> str | ||
Check whether the configuration point is set correctly. | ||
Arguments: | ||
config(dict): Dictionary of all configuration points | ||
Returns: | ||
A tuple (result, message) where result is whether the | ||
option is correctly configured, and message is an optional | ||
message describing the error. | ||
""" | ||
if not self.relevant(config): | ||
return (True, '') | ||
return self.check(config.get(self.name, None)) | ||
|
||
class Enabled(KernelConfig): | ||
def __init__(self, name, **kw): | ||
super(Enabled, self).__init__(name, 'Not Enabled', **kw) | ||
def check(self, config): | ||
return (bool(config), '') | ||
|
||
class EnabledIfPresent(Enabled): | ||
def check(self, config): | ||
if config is None: | ||
return (True, '') | ||
return super(EnabledIfPresent, self).check(config) | ||
|
||
class Disabled(KernelConfig): | ||
def __init__(self, name, **kw): | ||
super(Disabled, self).__init__(name, 'Not Disabled', **kw) | ||
def check(self, config): | ||
return (not bool(config), '') | ||
|
||
class Minimum(KernelConfig): | ||
def __init__(self, name, minimum, **kw): | ||
super(Minimum, self).__init__(name, 'Misconfigured', **kw) | ||
self.minimum = minimum | ||
def check(self, value): | ||
if value is None or value < self.minimum: | ||
return (False, "Should be at least {}".format(self.minimum)) | ||
return (True, '') | ||
|
||
kernel_configuration = [ | ||
# Enabled | ||
Enabled('STRICT_DEVMEM', requires=['DEVMEM']), | ||
Enabled('IO_STRICT_DEVMEM', requires=['DEVMEM']), | ||
Enabled('SYN_COOKIES'), | ||
Enabled('DEBUG_CREDENTIALS'), | ||
Enabled('DEBUG_NOTIFIERS'), | ||
Enabled('DEBUG_LIST'), | ||
Enabled('SECCOMP'), | ||
Enabled('SECCOMP_FILTER', minver=3.5, requires=['HAVE_ARCH_SECCOMP_FILTER']), | ||
Enabled('SECURITY'), | ||
# Enabled('SECURITY_YAMA'), | ||
Enabled('HARDENED_USERCOPY', minver=4.8), | ||
Enabled('SLAB_FREELIST_RANDOM', minver=4.7), | ||
# Enabled('SLUB_DEBUG'), | ||
# Enabled('PAGE_POISONING'), | ||
# Enabled('PAGE_POISONING_NO_SANITY'), | ||
# Enabled('PAGE_POISONING_ZERO'), | ||
Enabled('CC_STACKPROTECTOR', excludes=['CC_STACKPROTECTOR_STRONG']), | ||
Enabled('CC_STACKPROTECTOR_STRONG', minver=3.14), | ||
Enabled('PANIC_ON_OOPS', minver=3.5), | ||
Enabled('DEBUG_SET_MODULE_RONX', requires=['MODULES']), | ||
Enabled('DEBUG_RODATA'), | ||
Enabled('SECURITY_KPTR_RESTRICT'), | ||
Enabled('SECURITY_PERF_EVENTS_RESTRICT'), | ||
Enabled('SECURITY_DMESG_RESTRICT'), | ||
Enabled('SECURITY_NETWORK'), | ||
Enabled('SECURITY_SELINUX'), | ||
EnabledIfPresent('MSM_KERNEL_PROTECT'), | ||
|
||
# Not really security relevant, but come on guys | ||
Enabled('COREDUMP'), | ||
|
||
# Disabled | ||
Disabled('ACPI_CUSTOM_METHOD'), | ||
Disabled('COMPAT_BRK'), | ||
Disabled('DEVKMEM'), | ||
Disabled('DEVMEM'), | ||
Disabled('COMPAT_VDSO'), | ||
Disabled('KEXEC'), | ||
Disabled('BINFMT_MISC'), | ||
Disabled('LEGACY_PTYS'), | ||
Disabled('MODULES'), | ||
Disabled('KEXEC'), | ||
Disabled('SECURITY_SELINUX_DISABLE'), | ||
Disabled('MISC_FILESYSTEMS'), | ||
Disabled('SCSI_TGT'), | ||
Disabled('SCSI_CONSTANTS'), | ||
Disabled('SCSI_LOGGING'), | ||
Disabled('SCSI_SCAN_ASYNC'), | ||
Disabled('CONFIG_MEDIA_RADIO_SUPPORT'), | ||
Disabled('CONFIG_PFT'), | ||
Disabled('CONFIG_SYSVIPC'), | ||
|
||
|
||
# Permits reloading the kernel from disk | ||
Disabled('HIBERNATION'), | ||
|
||
# Prior to v4.1, assists heap memory attacks; best to keep interface disabled | ||
Disabled('INET_DIAG', maxver=4.1), | ||
|
||
|
||
# x86-specific | ||
Enabled('HIGHMEM64G', requires=['X86_32']), | ||
Enabled('X86_PAE', requires=['X86_32']), | ||
Disabled('LEGACY_VSYSCALL_NONE', requires=['X86_32', 'X86_64'], minver=4.4), | ||
Disabled('CONFIG_IA32_EMULATION'), | ||
Disabled('X86_X32'), | ||
Disabled('MODIFY_LDT_SYSCALL'), | ||
Minimum('DEFAULT_MMAP_MIN_ADDR', 65536, requires=['X86_32', 'X86_64']), | ||
Enabled('RANDOMIZE_BASE', excludes=['ARM']), | ||
Enabled('RANDOMIZE_MEMORY', requires=['X86_64']), | ||
|
||
# ARM specific | ||
Enabled('VMSPLIT_3G', requires=['ARM']), | ||
Disabled('STRICT_MEMORY_RWX', requires=['ARM', 'ARM64']), | ||
Minimum('DEFAULT_MMAP_MIN_ADDR', 32768, requires=['ARM', 'ARM64']), | ||
Minimum('ARCH_MMAP_RND_BITS', 16, requires=['ARM']), | ||
Minimum('ARCH_MMAP_RND_BITS', 24, requires=['ARM64']), | ||
Minimum('ARCH_MMAP_RND_COMPAT_BITS', 16, requires=['ARM64']), | ||
Enabled('CPU_SW_DOMAIN_PAN', requires=['ARM'], minver=4.3), | ||
Enabled('CONFIG_ARM64_PAN', requires=['ARM64'], minver=4.3), | ||
Disabled('OABI_COMPAT'), | ||
|
||
# Only relevant of MODULES are enabled | ||
Enabled('MODULE_SIG', requires=['MODULES']), | ||
Enabled('MODULE_SIG_FORCE', requires=['MODULES']), | ||
Enabled('MODULE_SIG_ALL', requires=['MODULES']), | ||
Enabled('MODULE_SIG_SHA512', requires=['MODULES']), | ||
Enabled('MODULE_SIG_KEY', requires=['MODULES']), | ||
|
||
] | ||
|
||
def parse_kconfig(data): | ||
"""Parses configuration data from a kernel .config. | ||
Arguments: | ||
data(str): Configuration contents. | ||
Returns: | ||
A :class:`dict` mapping configuration options. | ||
"Not set" is converted into ``None``, ``y`` and ``n`` are converted | ||
into :class:`bool`. Numbers are converted into :class:`int`. | ||
All other values are as-is. | ||
Each key has ``CONFIG_`` stripped from the beginning. | ||
Examples: | ||
>>> parse_kconfig('FOO=3') | ||
{'FOO': 3} | ||
>>> parse_kconfig('FOO=y') | ||
{'FOO': True} | ||
>>> parse_kconfig('FOO=n') | ||
{'FOO': False} | ||
>>> parse_kconfig('FOO=bar') | ||
{'FOO': 'bar'} | ||
>>> parse_kconfig('# FOO is not set') | ||
{'FOO': None} | ||
""" | ||
config = {} | ||
|
||
NOT_SET=' is not set' | ||
|
||
if not data: | ||
return | ||
|
||
for line in data.splitlines(): | ||
|
||
# Not set? Then set to None. | ||
if NOT_SET in line: | ||
line = line.split(NOT_SET, 1)[0] | ||
name = line.strip('# ') | ||
config[name] = None | ||
|
||
# Set to a value? Extract it | ||
if '=' in line: | ||
k, v = line.split('=', 1) | ||
|
||
# Boolean conversions | ||
if v == 'y': | ||
v = True | ||
elif v == 'n': | ||
v = False | ||
else: | ||
|
||
# Integer conversions | ||
try: | ||
v = int(v, 0) | ||
except ValueError: | ||
pass | ||
|
||
config[k] = v | ||
|
||
|
||
# Strip off all of the CONFIG_ prefixes | ||
config = ({k.replace('CONFIG_', ''): v for k,v in config.items()}) | ||
return config |
Oops, something went wrong.