Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow libcdb search offline database #2259

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pwnlib/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ def STDERR(v):
"""Sends logging to ``stderr`` by default, instead of ``stdout``"""
context.log_console = sys.stderr

def LOCAL_LIBCDB(v):
"""Sets local libcdb path"""
context.defaults['local_libcdb'] = v

hooks = {
'LOG_LEVEL': LOG_LEVEL,
'LOG_FILE': LOG_FILE,
Expand All @@ -170,6 +174,7 @@ def STDERR(v):
'NOASLR': NOASLR,
'NOPTRACE': NOPTRACE,
'STDERR': STDERR,
'LOCAL_LIBCDB': LOCAL_LIBCDB,
}

def initialize():
Expand Down
130 changes: 94 additions & 36 deletions pwnlib/commandline/libcdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from __future__ import division
from __future__ import print_function

import os
import re
import shutil
import subprocess
import sys

import pwnlib.args
Expand Down Expand Up @@ -46,15 +48,15 @@
lookup_parser.add_argument(
'--unstrip',
action = 'store_true',
default = True,
default = False,
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)

lookup_parser.add_argument(
'--no-unstrip',
action = 'store_false',
dest = 'unstrip',
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
'--offline',
action = 'store_true',
default = False,
help = 'Disable offline libcdb search mode'
)

hash_parser = libc_commands.add_parser(
Expand Down Expand Up @@ -89,15 +91,15 @@
hash_parser.add_argument(
'--unstrip',
action = 'store_true',
default = True,
default = False,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attempting to unstrip the libc should be enabled by default, so this can be cleaned up the other way around to only keep the --no-unstrip but default to True.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey, but I believe it would be better to set the default value of --no-unstrip to False. This way, I can pass not args.no_unstrip as the value for unstrip.

In the following code, by default, unstrip is True. If we use the --no-unstrip, the unstrip parameter will become False.

symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
matched_libcs = libcdb.search_by_symbol_offsets(symbols, raw=True, unstrip=not args.no_unstrip, offline=args.offline)
for libc in matched_libcs:
    print_libc(libc)
    fetch_libc(args, libc)

help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)

hash_parser.add_argument(
'--no-unstrip',
action = 'store_false',
dest = 'unstrip',
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
'--offline',
action = 'store_true',
default = False,
help = 'Disable offline libcdb search mode'
)

file_parser = libc_commands.add_parser(
Expand Down Expand Up @@ -134,19 +136,27 @@
help = 'Attempt to unstrip the libc binary inplace with debug symbols from a debuginfod server'
)

common_symbols = ['dup2', 'printf', 'puts', 'read', 'system', 'write']
download_parser = libc_commands.add_parser(
'download',
help = 'Download the libc-database repository',
description = 'Lookup a libc version by function offsets'
)

def find_libc(params):
import requests
url = "https://libc.rip/api/find"
result = requests.post(url, json=params, timeout=20)
log.debug('Request: %s', params)
log.debug('Result: %s', result.json())
if result.status_code != 200 or len(result.json()) == 0:
log.failure("Could not find libc for %s on libc.rip", params)
return []
download_parser.add_argument(
'categories',
metavar = 'categories',
nargs = '+',
help = 'Fetch the required libc category and symbol offsets. The parameter is the same as https://github.com/niklasb/libc-database/blob/master/get',
)

download_parser.add_argument(
'--save-path',
type = str,
help = 'Set the save path for libc-database.',
)

common_symbols = ['dup2', 'printf', 'puts', 'read', 'system', 'write']

return result.json()

def print_libc(libc):
log.info('%s', text.red(libc['id']))
Expand All @@ -158,13 +168,16 @@ def print_libc(libc):
for symbol in libc['symbols'].items():
log.indented('\t%25s = %s', symbol[0], symbol[1])

def handle_remote_libc(args, libc):
print_libc(libc)
def fetch_libc(args, libc, hash_type="build_id"):
hash_value = libc.get("buildid") or libc.get("build_id")

if args.download_libc:
path = libcdb.search_by_build_id(libc['buildid'], args.unstrip)
if hash_type == "buildid":
hash_type = "build_id"

path = libcdb.search_by_hash(hash_value, hash_type, args.unstrip, args.offline)

if path:
if args.unstrip:
libcdb.unstrip_libc(path)
shutil.copy(path, './{}.so'.format(libc['id']))

def translate_offset(offs, args, exe):
Expand All @@ -186,6 +199,14 @@ def collect_synthetic_symbols(exe):

return available_symbols

def dump_basic_symbols(exe, custom_symbols):
synthetic_symbols = collect_synthetic_symbols(exe)

symbols = common_symbols + (custom_symbols or []) + synthetic_symbols
symbols.sort()

return symbols

def main(args):
if len(sys.argv) < 3:
parser.print_usage()
Expand All @@ -196,25 +217,48 @@ def main(args):
if len(pairs) % 2 != 0:
log.failure('Uneven number of arguments. Please provide "symbol offset" pairs')
return

symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
matched_libcs = find_libc({'symbols': symbols})
matched_libcs = libcdb.search_by_symbol_offsets(symbols, raw=True, unstrip=args.unstrip, offline=args.offline)
for libc in matched_libcs:
handle_remote_libc(args, libc)
print_libc(libc)
fetch_libc(args, libc)

elif args.libc_command == 'hash':
for hash_value in args.hash_value:
matched_libcs = find_libc({args.hash_type: hash_value})
for libc in matched_libcs:
handle_remote_libc(args, libc)
libs_id = None
hash_type = args.hash_type if args.hash_type != "buildid" else "build_id"

# Search and download libc
exe_path = libcdb.search_by_hash(hash_value, hash_type, args.unstrip, args.offline)

if exe_path:
libs_id = read(exe_path + ".id").decode()

if not libs_id:
continue

# Dump common symbols
exe = ELF(exe_path, checksec=False)
symbols = {}
for symbol in dump_basic_symbols(exe, []):
if symbol in exe.symbols:
symbols[symbol] = exe.symbols[symbol]
else:
symbols[symbol] = 0
log.warn('%s is not found', symbol)

libc = libcdb._pack_libs_info(exe_path, libs_id, "", symbols)
print_libc(libc)
fetch_libc(args, libc, hash_type)

elif args.libc_command == 'file':
from hashlib import md5, sha1, sha256
for file in args.files:
if not os.path.exists(file) or not os.path.isfile(file):
log.failure('File does not exist %s', args.file)
continue

if args.unstrip:
libcdb.unstrip_libc(file)

Expand All @@ -233,15 +277,29 @@ def main(args):

# Always dump the basic list of common symbols
log.indented('%s', text.green('Symbols:'))
synthetic_symbols = collect_synthetic_symbols(exe)
symbols = dump_basic_symbols(exe, args.symbols)

symbols = common_symbols + (args.symbols or []) + synthetic_symbols
symbols.sort()
for symbol in symbols:
if symbol not in exe.symbols:
log.indented('%25s = %s', symbol, text.red('not found'))
else:
log.indented('%25s = %#x', symbol, translate_offset(exe.symbols[symbol], args, exe))

elif args.libc_command == 'download':
if args.save_path:
save_path = os.path.abspath(args.save_path)
else:
save_path = context.local_libcdb

if not os.path.exists(save_path):
log.info("Download libc-database to %s", save_path)
process = subprocess.Popen(["git", "clone", "https://github.com/niklasb/libc-database", save_path])
process.wait()

log.info("Fetch libc categories and symbol offsets")
process = subprocess.Popen(["./get"] + args.categories, cwd=save_path)
process.wait()


if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
5 changes: 5 additions & 0 deletions pwnlib/context/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ class ContextType(object):
'endian': 'little',
'gdbinit': "",
'kernel': None,
'local_libcdb': "/var/lib/libc-database",
'log_level': logging.INFO,
'log_file': _devnull(),
'log_console': sys.stdout,
Expand Down Expand Up @@ -1070,6 +1071,10 @@ def log_console(self, stream):
stream = open(stream, 'wt')
return stream

@_validator
def local_libcdb(self, path):
return path

@property
def mask(self):
return (1 << self.bits) - 1
Expand Down
Loading