Skip to content

Commit 10d1c5a

Browse files
authored
Merge branch 'dev' into basic-darwin
2 parents 954d546 + bf7abc0 commit 10d1c5a

16 files changed

+315
-90
lines changed

.github/workflows/android.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- uses: actions/checkout@v4
1515

1616
- name: Cache for pip
17-
uses: actions/cache@v3
17+
uses: actions/cache@v4
1818
id: cache-pip
1919
with:
2020
path: ~/.cache/pip

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
git log --oneline --graph -10
2121
2222
- name: Cache for pip
23-
uses: actions/cache@v3
23+
uses: actions/cache@v4
2424
id: cache-pip
2525
with:
2626
path: ~/.cache/pip

.github/workflows/lint.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
steps:
1313
- uses: actions/checkout@v4
1414
- name: Cache for pip
15-
uses: actions/cache@v3
15+
uses: actions/cache@v4
1616
id: cache-pip
1717
with:
1818
path: ~/.cache/pip

.github/workflows/pylint.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
steps:
1313
- uses: actions/checkout@v4
1414
- name: Cache for pip
15-
uses: actions/cache@v3
15+
uses: actions/cache@v4
1616
id: cache-pip
1717
with:
1818
path: ~/.cache/pip

pwnlib/asm.py

+2
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,8 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
923923

924924

925925
lines = []
926+
927+
# Note: those patterns are also used in pwnlib/commandline/disasm.py
926928
pattern = '^( *[0-9a-f]+: *)', '((?:[0-9a-f]+ )+ *)', '(.*)'
927929
if not byte:
928930
pattern = pattern[::2]

pwnlib/commandline/disasm.py

+27-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import print_function
44

55
import argparse
6+
import re
67
import string
78
import sys
89

@@ -76,18 +77,34 @@ def main(args):
7677
from pygments.formatters import TerminalFormatter
7778
from pwnlib.lexer import PwntoolsLexer
7879

79-
offsets = disasm(dat, vma=safeeval.const(args.address), instructions=False, byte=False)
80-
bytes = disasm(dat, vma=safeeval.const(args.address), instructions=False, offset=False)
81-
instrs = disasm(dat, vma=safeeval.const(args.address), byte=False, offset=False)
82-
# instrs = highlight(instrs, PwntoolsLexer(), TerminalFormatter())
80+
dis = disasm(dat, vma=safeeval.const(args.address))
8381

84-
highlight_bytes = lambda t: ''.join(map(lambda x: x.replace('00', text.red('00')).replace('0a', text.red('0a')), group(2, t)))
85-
for o,b,i in zip(*map(str.splitlines, (offsets, bytes, instrs))):
86-
b = ' '.join(highlight_bytes(bb) for bb in b.split(' '))
87-
i = highlight(i.strip(), PwntoolsLexer(), TerminalFormatter()).strip()
88-
i = i.replace(',',', ')
82+
# Note: those patterns are copied from disasm function
83+
pattern = '^( *[0-9a-f]+: *)((?:[0-9a-f]+ )+ *)(.*)'
84+
lines = []
85+
for line in dis.splitlines():
86+
match = re.search(pattern, line)
87+
if not match:
88+
# Append as one element tuple
89+
lines.append((line,))
90+
continue
91+
92+
groups = match.groups()
93+
o, b, i = groups
94+
95+
lines.append((o, b, i))
8996

90-
print(o,b,i)
97+
98+
highlight_bytes = lambda t: ''.join(map(lambda x: x.replace('00', text.red('00')).replace('0a', text.red('0a')), group(2, t)))
99+
for line in lines:
100+
if len(line) == 3:
101+
o, b, i = line
102+
b = ' '.join(highlight_bytes(bb) for bb in b.split(' '))
103+
i = highlight(i.strip(), PwntoolsLexer(), TerminalFormatter()).strip()
104+
i = i.replace(',',', ')
105+
print(o,b,i)
106+
else:
107+
print(line[0])
91108
return
92109

93110
print(disasm(dat, vma=safeeval.const(args.address)))

pwnlib/elf/elf.py

+34-18
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ def debug(self, argv=[], *a, **kw):
448448

449449
def _describe(self, *a, **kw):
450450
log.info_once(
451-
'%s\n%-10s%s-%s-%s\n%s',
451+
'%s\n%-12s%s-%s-%s\n%s',
452452
repr(self.path),
453453
'Arch:',
454454
self.arch,
@@ -2002,6 +2002,16 @@ def packed(self):
20022002
""":class:`bool`: Whether the current binary is packed with UPX."""
20032003
return b'UPX!' in self.get_data()[:0xFF]
20042004

2005+
@property
2006+
def stripped(self):
2007+
""":class:`bool`: Whether the current binary has been stripped of symbols"""
2008+
return not any(section['sh_type'] == 'SHT_SYMTAB' for section in self.iter_sections())
2009+
2010+
@property
2011+
def debuginfo(self):
2012+
""":class:`bool`: Whether the current binary has debug information"""
2013+
return self.get_section_by_name('.debug_info') is not None
2014+
20052015
@property
20062016
def pie(self):
20072017
""":class:`bool`: Whether the current binary is position-independent."""
@@ -2045,68 +2055,74 @@ def checksec(self, banner=True, color=True):
20452055

20462056
# Kernel version?
20472057
if self.version and self.version != (0,):
2048-
res.append('Version:'.ljust(10) + '.'.join(map(str, self.version)))
2058+
res.append('Version:'.ljust(12) + '.'.join(map(str, self.version)))
20492059
if self.build:
2050-
res.append('Build:'.ljust(10) + self.build)
2060+
res.append('Build:'.ljust(12) + self.build)
20512061

20522062
res.extend([
2053-
"RELRO:".ljust(10) + {
2063+
"RELRO:".ljust(12) + {
20542064
'Full': green("Full RELRO"),
20552065
'Partial': yellow("Partial RELRO"),
20562066
None: red("No RELRO")
20572067
}[self.relro],
2058-
"Stack:".ljust(10) + {
2068+
"Stack:".ljust(12) + {
20592069
True: green("Canary found"),
20602070
False: red("No canary found")
20612071
}[self.canary],
2062-
"NX:".ljust(10) + {
2072+
"NX:".ljust(12) + {
20632073
True: green("NX enabled"),
20642074
False: red("NX disabled"),
20652075
None: yellow("NX unknown - GNU_STACK missing"),
20662076
}[self.nx],
2067-
"PIE:".ljust(10) + {
2077+
"PIE:".ljust(12) + {
20682078
True: green("PIE enabled"),
20692079
False: red("No PIE (%#x)" % self.address)
20702080
}[self.pie],
20712081
])
20722082

20732083
# Execstack may be a thing, even with NX enabled, because of glibc
20742084
if self.execstack and self.nx is not False:
2075-
res.append("Stack:".ljust(10) + red("Executable"))
2085+
res.append("Stack:".ljust(12) + red("Executable"))
20762086

20772087
# Are there any RWX areas in the binary?
20782088
#
20792089
# This will occur if NX is disabled and *any* area is
20802090
# RW, or can expressly occur.
20812091
if self.rwx_segments or (not self.nx and self.writable_segments):
2082-
res += [ "RWX:".ljust(10) + red("Has RWX segments") ]
2092+
res += [ "RWX:".ljust(12) + red("Has RWX segments") ]
20832093

20842094
if self.rpath:
2085-
res += [ "RPATH:".ljust(10) + red(repr(self.rpath)) ]
2095+
res += [ "RPATH:".ljust(12) + red(repr(self.rpath)) ]
20862096

20872097
if self.runpath:
2088-
res += [ "RUNPATH:".ljust(10) + red(repr(self.runpath)) ]
2098+
res += [ "RUNPATH:".ljust(12) + red(repr(self.runpath)) ]
20892099

20902100
if self.packed:
2091-
res.append('Packer:'.ljust(10) + red("Packed with UPX"))
2101+
res.append('Packer:'.ljust(12) + red("Packed with UPX"))
20922102

20932103
if self.fortify:
2094-
res.append("FORTIFY:".ljust(10) + green("Enabled"))
2104+
res.append("FORTIFY:".ljust(12) + green("Enabled"))
20952105

20962106
if self.asan:
2097-
res.append("ASAN:".ljust(10) + green("Enabled"))
2107+
res.append("ASAN:".ljust(12) + green("Enabled"))
20982108

20992109
if self.msan:
2100-
res.append("MSAN:".ljust(10) + green("Enabled"))
2110+
res.append("MSAN:".ljust(12) + green("Enabled"))
21012111

21022112
if self.ubsan:
2103-
res.append("UBSAN:".ljust(10) + green("Enabled"))
2113+
res.append("UBSAN:".ljust(12) + green("Enabled"))
21042114

21052115
if self.shadowstack:
2106-
res.append("SHSTK:".ljust(10) + green("Enabled"))
2116+
res.append("SHSTK:".ljust(12) + green("Enabled"))
21072117

21082118
if self.ibt:
2109-
res.append("IBT:".ljust(10) + green("Enabled"))
2119+
res.append("IBT:".ljust(12) + green("Enabled"))
2120+
2121+
if not self.stripped:
2122+
res.append("Stripped:".ljust(12) + red("No"))
2123+
2124+
if self.debuginfo:
2125+
res.append("Debuginfo:".ljust(12) + red("Yes"))
21102126

21112127
# Check for Linux configuration, it must contain more than
21122128
# just the version.

pwnlib/gdb.py

+27-10
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,10 @@ def __init__(self, conn, *args, **kwargs):
652652
"""
653653
# Creates a real breakpoint and connects it with this mirror
654654
self.conn = conn
655-
self.server_breakpoint = conn.root.set_breakpoint(
655+
self.server_breakpoint = self._server_set_breakpoint(*args, **kwargs)
656+
657+
def _server_set_breakpoint(self, *args, **kwargs):
658+
return self.conn.root.set_breakpoint(
656659
self, hasattr(self, 'stop'), *args, **kwargs)
657660

658661
def __getattr__(self, item):
@@ -670,25 +673,43 @@ def __getattr__(self, item):
670673
raise AttributeError()
671674
return getattr(self.server_breakpoint, item)
672675

676+
def __setattr__(self, name, value):
677+
"""Set attributes of the real breakpoint."""
678+
if name in (
679+
'enabled',
680+
'silent',
681+
'thread',
682+
'task',
683+
'ignore_count',
684+
'hit_count'
685+
'condition',
686+
'commands',
687+
):
688+
return setattr(self.server_breakpoint, name, value)
689+
return super().__setattr__(name, value)
690+
673691
def exposed_stop(self):
674692
# Handle stop() call from the server.
675693
return self.stop()
676694

677-
class FinishBreakpoint:
695+
class FinishBreakpoint(Breakpoint):
678696
"""Mirror of ``gdb.FinishBreakpoint`` class.
679697
680698
See https://sourceware.org/gdb/onlinedocs/gdb/Finish-Breakpoints-in-Python.html
681699
for more information.
682700
"""
683701

684-
def __init__(self, conn, *args, **kwargs):
702+
def __init__(self, *args, **kwargs):
685703
"""Do not create instances of this class directly.
686704
687705
Use ``pwnlib.gdb.Gdb.FinishBreakpoint`` instead.
688706
"""
689-
# Creates a real finish breakpoint and connects it with this mirror
690-
self.conn = conn
691-
self.server_breakpoint = conn.root.set_finish_breakpoint(
707+
# See https://github.com/pylint-dev/pylint/issues/4228
708+
# pylint: disable=useless-super-delegation
709+
super().__init__(*args, **kwargs)
710+
711+
def _server_set_breakpoint(self, *args, **kwargs):
712+
return self.conn.root.set_finish_breakpoint(
692713
self, hasattr(self, 'stop'), hasattr(self, 'out_of_scope'),
693714
*args, **kwargs)
694715

@@ -708,10 +729,6 @@ def __getattr__(self, item):
708729
raise AttributeError()
709730
return getattr(self.server_breakpoint, item)
710731

711-
def exposed_stop(self):
712-
# Handle stop() call from the server.
713-
return self.stop()
714-
715732
def exposed_out_of_scope(self):
716733
# Handle out_of_scope() call from the server.
717734
return self.out_of_scope()

pwnlib/gdb_api_bridge.py

+1
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,6 @@ def exposed_quit(self):
110110
socket_path=socket_path,
111111
protocol_config={
112112
'allow_all_attrs': True,
113+
'allow_setattr': True,
113114
},
114115
).start)

pwnlib/libcdb.py

+41-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from __future__ import division
66

77
import os
8+
import time
89
import six
910
import tempfile
1011

@@ -13,14 +14,20 @@
1314
from pwnlib.log import getLogger
1415
from pwnlib.tubes.process import process
1516
from pwnlib.util.fiddling import enhex
17+
from pwnlib.util.hashes import sha1filehex, sha256filehex, md5filehex
1618
from pwnlib.util.misc import read
1719
from pwnlib.util.misc import which
1820
from pwnlib.util.misc import write
1921
from pwnlib.util.web import wget
2022

2123
log = getLogger(__name__)
2224

23-
HASHES = ['build_id', 'sha1', 'sha256', 'md5']
25+
HASHES = {
26+
'build_id': lambda path: enhex(ELF(path, checksec=False).buildid or b''),
27+
'sha1': sha1filehex,
28+
'sha256': sha256filehex,
29+
'md5': md5filehex,
30+
}
2431
DEBUGINFOD_SERVERS = [
2532
'https://debuginfod.elfutils.org/',
2633
]
@@ -29,6 +36,9 @@
2936
urls = os.environ['DEBUGINFOD_URLS'].split(' ')
3037
DEBUGINFOD_SERVERS = urls + DEBUGINFOD_SERVERS
3138

39+
# Retry failed lookups after some time
40+
NEGATIVE_CACHE_EXPIRY = 60 * 60 * 24 * 7 # 1 week
41+
3242
# https://gitlab.com/libcdb/libcdb wasn't updated after 2019,
3343
# but still is a massive database of older libc binaries.
3444
def provider_libcdb(hex_encoded_id, hash_type):
@@ -100,7 +110,23 @@ def provider_libc_rip(hex_encoded_id, hash_type):
100110
return None
101111
return data
102112

103-
PROVIDERS = [provider_libcdb, provider_libc_rip]
113+
# Check if the local system libc matches the requested hash.
114+
def provider_local_system(hex_encoded_id, hash_type):
115+
if hash_type == 'id':
116+
return None
117+
shell_path = os.environ.get('SHELL', None) or '/bin/sh'
118+
if not os.path.exists(shell_path):
119+
log.debug('Shell path %r does not exist. Skipping local system libc matching.', shell_path)
120+
return None
121+
local_libc = ELF(shell_path, checksec=False).libc
122+
if not local_libc:
123+
log.debug('Cannot lookup libc from shell %r. Skipping local system libc matching.', shell_path)
124+
return None
125+
if HASHES[hash_type](local_libc.path) == hex_encoded_id:
126+
return local_libc.data
127+
return None
128+
129+
PROVIDERS = [provider_local_system, provider_libcdb, provider_libc_rip]
104130

105131
def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
106132
assert hash_type in HASHES, hash_type
@@ -109,6 +135,10 @@ def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
109135
cache, cache_valid = _check_elf_cache('libcdb', hex_encoded_id, hash_type)
110136
if cache_valid:
111137
return cache
138+
139+
# We searched for this buildid before, but didn't find anything.
140+
if cache is None:
141+
return None
112142

113143
# Run through all available libc database providers to see if we have a match.
114144
for provider in PROVIDERS:
@@ -141,6 +171,10 @@ def _search_debuginfo_by_hash(base_url, hex_encoded_id):
141171
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
142172
if cache_valid:
143173
return cache
174+
175+
# We searched for this buildid before, but didn't find anything.
176+
if cache is None:
177+
return None
144178

145179
# Try to find separate debuginfo.
146180
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
@@ -191,8 +225,11 @@ def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
191225

192226
data = read(cache)
193227
if not data.startswith(b'\x7FELF'):
194-
log.info_once("Skipping unavailable ELF %s", hex_encoded_id)
195-
return cache, False
228+
# Retry failed lookups after some time
229+
if time.time() > os.path.getmtime(cache) + NEGATIVE_CACHE_EXPIRY:
230+
return cache, False
231+
log.info_once("Skipping invalid cached ELF %s", hex_encoded_id)
232+
return None, False
196233

197234
log.info_once("Using cached data from %r", cache)
198235
return cache, True

0 commit comments

Comments
 (0)