Skip to content

Commit 9dacd1b

Browse files
Merge branch 'stable' into beta
2 parents 5136823 + 74a5876 commit 9dacd1b

File tree

5 files changed

+142
-18
lines changed

5 files changed

+142
-18
lines changed

pwnlib/dynelf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ def _find_linkmap(self, pltgot=None, debug=None):
447447
if debug:
448448
w.status("r_debug.linkmap")
449449
linkmap = self.leak.field(debug, r_debug.r_map)
450-
w.status("r_debug.linkmap %#x" % result)
450+
w.status("r_debug.linkmap %#x" % linkmap)
451451

452452
if not linkmap:
453453
w.failure("Could not find DT_PLTGOT or DT_DEBUG")

pwnlib/elf/corefile.py

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ class Corefile(ELF):
389389
390390
>>> core.vdso.data[:4]
391391
'\x7fELF'
392-
>>> core.libc # docteset: +ELLIPSIS
392+
>>> core.libc # doctest: +ELLIPSIS
393393
Mapping('/lib/x86_64-linux-gnu/libc-...', ...)
394394
395395
The corefile also contains a :attr:`.Corefile.stack` property, which gives
@@ -421,14 +421,54 @@ class Corefile(ELF):
421421
422422
>>> s = ssh('travis', 'example.pwnme')
423423
>>> _ = s.set_working_directory()
424-
>>> elf = ELF.from_assembly('int3')
424+
>>> elf = ELF.from_assembly(shellcraft.trap())
425425
>>> path = s.upload(elf.path)
426426
>>> _ =s.chmod('+x', path)
427427
>>> io = s.process(path)
428428
>>> io.wait()
429429
-1
430430
>>> io.corefile.signal == signal.SIGTRAP # doctest: +SKIP
431431
True
432+
433+
Tests:
434+
435+
These are extra tests not meant to serve as examples.
436+
437+
Corefile.getenv() works correctly, even if the environment variable's
438+
value contains embedded '='. Corefile is able to find the stack, even
439+
if the stack pointer doesn't point at the stack.
440+
441+
>>> elf = ELF.from_assembly(shellcraft.crash())
442+
>>> io = elf.process(env={'FOO': 'BAR=BAZ'})
443+
>>> io.wait()
444+
>>> core = io.corefile
445+
>>> core.getenv('FOO')
446+
'BAR=BAZ'
447+
>>> core.sp == 0
448+
True
449+
>>> core.sp in core.stack
450+
False
451+
452+
Corefile gracefully handles the stack being filled with garbage, including
453+
argc / argv / envp being overwritten.
454+
455+
>>> context.clear(arch='i386')
456+
>>> assembly = '''
457+
... LOOP:
458+
... mov dword ptr [esp], 0x41414141
459+
... pop eax
460+
... jmp LOOP
461+
... '''
462+
>>> elf = ELF.from_assembly(assembly)
463+
>>> io = elf.process()
464+
>>> io.wait()
465+
>>> core = io.corefile
466+
>>> core.argc, core.argv, core.env
467+
(0, [], {})
468+
>>> core.stack.data.endswith('AAAA')
469+
True
470+
>>> core.fault_addr == core.sp
471+
True
432472
"""
433473

434474
_fill_gaps = False
@@ -454,6 +494,9 @@ def __init__(self, *a, **kw):
454494
#: variable.
455495
#:
456496
#: Note: Use with the :meth:`.ELF.string` method to extract them.
497+
#:
498+
#: Note: If FOO=BAR is in the environment, self.env['FOO'] is the
499+
#: address of the string "BAR\x00".
457500
self.env = {}
458501

459502
#: :class:`list`: List of addresses of arguments on the stack.
@@ -664,7 +707,22 @@ def ppid(self):
664707

665708
@property
666709
def signal(self):
667-
""":class:`int`: Signal which caused the core to be dumped."""
710+
""":class:`int`: Signal which caused the core to be dumped.
711+
712+
Example:
713+
714+
>>> elf = ELF.from_assembly(shellcraft.trap())
715+
>>> io = elf.process()
716+
>>> io.wait()
717+
>>> io.corefile.signal == signal.SIGTRAP
718+
True
719+
720+
>>> elf = ELF.from_assembly(shellcraft.crash())
721+
>>> io = elf.process()
722+
>>> io.wait()
723+
>>> io.corefile.signal == signal.SIGSEGV
724+
True
725+
"""
668726
if self.siginfo:
669727
return int(self.siginfo.si_signo)
670728
if self.prstatus:
@@ -675,7 +733,17 @@ def fault_addr(self):
675733
""":class:`int`: Address which generated the fault, for the signals
676734
SIGILL, SIGFPE, SIGSEGV, SIGBUS. This is only available in native
677735
core dumps created by the kernel. If the information is unavailable,
678-
this returns the address of the instruction pointer."""
736+
this returns the address of the instruction pointer.
737+
738+
739+
Example:
740+
741+
>>> elf = ELF.from_assembly('mov eax, 0xdeadbeef; jmp eax', arch='i386')
742+
>>> io = elf.process()
743+
>>> io.wait()
744+
>>> io.corefile.fault_addr == io.corefile.eax == 0xdeadbeef
745+
True
746+
"""
679747
if self.siginfo:
680748
return int(self.siginfo.sigfault_addr)
681749

@@ -848,11 +916,31 @@ def _parse_stack(self):
848916
# Now we can fill in the environment
849917
env_pointer_data = stack[start_of_envp:p_last_env_addr+self.bytes]
850918
for pointer in unpack_many(env_pointer_data):
851-
end = stack.find('=', last_env_addr)
852919

853-
if pointer in stack and end in stack:
854-
name = stack[pointer:end]
855-
self.env[name] = pointer
920+
# If the stack is corrupted, the pointer will be outside of
921+
# the stack.
922+
if pointer not in stack:
923+
continue
924+
925+
try:
926+
name_value = self.string(pointer)
927+
except Exception:
928+
continue
929+
930+
name, value = name_value.split('=', 1)
931+
932+
# "end" points at the byte after the null terminator
933+
end = pointer + len(name_value) + 1
934+
935+
# Do not mark things as environment variables if they point
936+
# outside of the stack itself, or we had to cross into a different
937+
# mapping (after the stack) to read it.
938+
# This may occur when the entire stack is filled with non-NUL bytes,
939+
# and we NULL-terminate on a read failure in .string().
940+
if end not in stack:
941+
continue
942+
943+
self.env[name] = pointer + len(name) + len('=')
856944

857945
# May as well grab the arguments off the stack as well.
858946
# argc comes immediately before argv[0] on the stack, but
@@ -904,15 +992,32 @@ def getenv(self, name):
904992
905993
Returns:
906994
:class:`str`: The contents of the environment variable.
995+
996+
Example:
997+
998+
>>> elf = ELF.from_assembly(shellcraft.trap())
999+
>>> io = elf.process(env={'GREETING': 'Hello!'})
1000+
>>> io.wait()
1001+
>>> io.corefile.getenv('GREETING')
1002+
'Hello!'
9071003
"""
9081004
if name not in self.env:
9091005
log.error("Environment variable %r not set" % name)
9101006

911-
return self.string(self.env[name]).split('=',1)[-1]
1007+
return self.string(self.env[name])
9121008

9131009
@property
9141010
def registers(self):
915-
""":class:`dict`: All available registers in the coredump."""
1011+
""":class:`dict`: All available registers in the coredump.
1012+
1013+
Example:
1014+
1015+
>>> elf = ELF.from_assembly('mov eax, 0xdeadbeef;' + shellcraft.trap(), arch='i386')
1016+
>>> io = elf.process()
1017+
>>> io.wait()
1018+
>>> io.corefile.registers['eax'] == 0xdeadbeef
1019+
True
1020+
"""
9161021
if not self.prstatus:
9171022
return {}
9181023

@@ -1020,6 +1125,7 @@ def __init__(self, proc):
10201125
new_path = 'core.%i' % core_pid
10211126
if core_pid > 0 and new_path != self.core_path:
10221127
write(new_path, self.read(self.core_path))
1128+
self.unlink(self.core_path)
10231129
self.core_path = new_path
10241130

10251131
# Check the PID

pwnlib/elf/elf.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,16 +1686,33 @@ def unpack(self, address, *a, **kw):
16861686
return packing.unpack(self.read(address, context.bytes), *a, **kw)
16871687

16881688
def string(self, address):
1689-
"""Reads a null-terminated string from the specified ``address``"""
1689+
"""string(address) -> str
1690+
1691+
Reads a null-terminated string from the specified ``address``
1692+
1693+
Returns:
1694+
A ``str`` with the string contents (NUL terminator is omitted),
1695+
or an empty string if no NUL terminator could be found.
1696+
"""
16901697
data = ''
16911698
while True:
1692-
c = self.read(address, 1)
1699+
read_size = 0x1000
1700+
partial_page = address & 0xfff
1701+
1702+
if partial_page:
1703+
read_size -= partial_page
1704+
1705+
c = self.read(address, read_size)
1706+
16931707
if not c:
16941708
return ''
1695-
if c == '\x00':
1696-
return data
1709+
16971710
data += c
1698-
address += 1
1711+
1712+
if '\x00' in c:
1713+
return data[:data.index('\x00')]
1714+
1715+
address += len(c)
16991716

17001717
def flat(self, address, *a, **kw):
17011718
"""Writes a full array of values to the specified address.

pwnlib/tubes/ssh.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ def elf(self):
341341
libs = self.parent.libs(self.executable)
342342

343343
for lib in libs:
344-
if self.executable in lib:
344+
# Cannot just check "executable in lib", see issue #1047
345+
if lib.endswith(self.executable):
345346
return pwnlib.elf.elf.ELF(lib)
346347

347348

travis/install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ setup_android_emulator()
9090
elif [[ -n "$TRAVIS_TAG" ]]; then
9191
echo "TRAVIS_TAG ($TRAVIS_TAG) indicates a new relase"
9292
echo "Forcing Android Emulator installation"
93-
elif (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -iE "android|adb"); then
93+
elif (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -iE "android|adb" | grep -v "commit "); then
9494
echo "Found Android-related commits, forcing Android Emulator installation"
9595
else
9696
# In order to avoid running the doctests that require the Android

0 commit comments

Comments
 (0)