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

Fix Safe-Linking (GLIBC >= 2.32) and malloc_state struct #878

Merged
merged 6 commits into from
Oct 13, 2022
Merged
155 changes: 77 additions & 78 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,9 +1224,9 @@ def __init__(self, addr: Union[str, int]) -> None:
return

def reset(self):
self.__sizeof = ctypes.sizeof(GlibcHeapInfo.heap_info_t())
self.__data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t()))
self.__heap_info = GlibcArena.malloc_state_t().from_buffer_copy(self.__data)
self._sizeof = ctypes.sizeof(GlibcHeapInfo.heap_info_t())
self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t()))
self.__heap_info = GlibcArena.malloc_state_t().from_buffer_copy(self._data)
return

def __getattr__(self, item: Any) -> Any:
Expand All @@ -1246,7 +1246,7 @@ def address(self) -> int:

@property
def sizeof(self) -> int:
return self.__sizeof
return self._sizeof

@property
def addr(self) -> int:
Expand All @@ -1268,22 +1268,29 @@ def malloc_state_t() -> Type[ctypes.Structure]:
pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32
fields = [
("mutex", ctypes.c_uint32),
("have_fastchunks", ctypes.c_uint32),
("flags", ctypes.c_uint32),
]
if gef and gef.libc.version and gef.libc.version >= (2, 27):
# https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L1684
fields += [
("UNUSED_c", ctypes.c_uint32), # padding to align to 0x10
("fastbinsY", GlibcArena.NFASTBINS * pointer),
("have_fastchunks", ctypes.c_uint32),
("UNUSED_c", ctypes.c_uint32), # padding to align to 0x10
]
fields += [
("fastbinsY", GlibcArena.NFASTBINS * pointer),
("top", pointer),
("last_remainder", pointer),
("bins", (GlibcArena.NBINS * 2 - 2) * pointer),
("binmap", GlibcArena.BINMAPSIZE * ctypes.c_uint32),
("next", pointer),
("next_free", pointer),
("attached_threads", pointer),
("next_free", pointer)
]
if gef and gef.libc.version and gef.libc.version >= (2, 23):
# https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1719
fields += [
("attached_threads", pointer)
]
fields += [
("system_mem", pointer),
("max_system_mem", pointer),
]
Expand All @@ -1302,9 +1309,9 @@ def __init__(self, addr: str) -> None:
return

def reset(self):
self.__sizeof = ctypes.sizeof(GlibcArena.malloc_state_t())
self.__data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t()))
self.__arena = GlibcArena.malloc_state_t().from_buffer_copy(self.__data)
self._sizeof = ctypes.sizeof(GlibcArena.malloc_state_t())
self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t()))
self.__arena = GlibcArena.malloc_state_t().from_buffer_copy(self._data)
return

def __abs__(self) -> int:
Expand Down Expand Up @@ -1336,15 +1343,15 @@ def __str__(self) -> str:
return (f"{Color.colorify('Arena', 'blue bold underline')}({properties})")

def __repr__(self) -> str:
return f"GlibcArena(address={self.__address:#x}, size={self.__sizeof})"
return f"GlibcArena(address={self.__address:#x}, size={self._sizeof})"

@property
def address(self) -> int:
return self.__address

@property
def sizeof(self) -> int:
return self.__sizeof
return self._sizeof

@property
def addr(self) -> int:
Expand All @@ -1360,8 +1367,6 @@ def last_remainder(self) -> int:

@property
def fastbinsY(self) -> ctypes.Array:
if not gef.libc.version >= (2, 27):
raise RuntimeError
return self.__arena.fastbinsY

@property
Expand Down Expand Up @@ -1392,12 +1397,12 @@ def system_mem(self) -> int:
def max_system_mem(self) -> int:
return self.__arena.max_system_mem

def fastbin(self, i: int) -> Optional["GlibcChunk"]:
def fastbin(self, i: int) -> Optional["GlibcFastChunk"]:
"""Return head chunk in fastbinsY[i]."""
addr = int(self.fastbinsY[i])
if addr == 0:
return None
return GlibcChunk(addr + 2 * gef.arch.ptrsize)
return GlibcFastChunk(addr + 2 * gef.arch.ptrsize)

def bin(self, i: int) -> Tuple[int, int]:
idx = i * 2
Expand Down Expand Up @@ -1492,40 +1497,39 @@ def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = T
return

def reset(self):
self.__sizeof = ctypes.sizeof(GlibcChunk.malloc_chunk_t())
self.__data = gef.memory.read(
self._sizeof = ctypes.sizeof(GlibcChunk.malloc_chunk_t())
self._data = gef.memory.read(
self.base_address, ctypes.sizeof(GlibcChunk.malloc_chunk_t()))
self.__chunk = GlibcChunk.malloc_chunk_t().from_buffer_copy(self.__data)
self._chunk = GlibcChunk.malloc_chunk_t().from_buffer_copy(self._data)
return

@property
def prev_size(self) -> int:
return self.__chunk.prev_size
return self._chunk.prev_size

@property
def size(self) -> int:
return self.__chunk.size & (~0x07)
return self._chunk.size & (~0x07)

@property
def flags(self) -> ChunkFlags:
return GlibcChunk.ChunkFlags(self.__chunk.size & 0x07)
return GlibcChunk.ChunkFlags(self._chunk.size & 0x07)

@property
def fd(self) -> int:
assert(gef and gef.libc.version)
return self.__chunk.fd if gef.libc.version < (2, 32) else self.reveal_ptr(self.data_address)
return self._chunk.fd

@property
def bk(self) -> int:
return self.__chunk.bk
return self._chunk.bk

@property
def fd_nextsize(self) -> int:
return self.__chunk.fd_nextsize
return self._chunk.fd_nextsize

@property
def bk_nextsize(self) -> int:
return self.__chunk.bk_nextsize
return self._chunk.bk_nextsize

def get_usable_size(self) -> int:
# https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537
Expand Down Expand Up @@ -1573,41 +1577,7 @@ def get_next_chunk(self, allow_unaligned: bool = False) -> "GlibcChunk":

def get_next_chunk_addr(self) -> int:
return self.data_address + self.size

def protect_ptr(self, pos: int, pointer: int) -> int:
"""https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L339"""
assert(gef and gef.libc.version)
if gef.libc.version < (2, 32):
return pointer
return (pos >> 12) ^ pointer

def reveal_ptr(self, pointer: int) -> int:
"""https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L341"""
assert(gef and gef.libc.version)
if gef.libc.version < (2, 32):
return pointer
return gef.memory.read_integer(pointer) ^ (pointer >> 12)

# obsolete functions
@deprecated(f"Use `GlibcChunk.fd`")
def get_fwd_ptr(self) -> int:
return self.fd

@deprecated(f"Use `GlibcChunk.bk`")
def get_bkw_ptr(self) -> int:
return gef.memory.read_integer(self.data_address + gef.arch.ptrsize)

@property
def fwd(self) -> int:
warn(f"[Obsolete] `GlibcChunk.fwd` is deprecated, use `GlibcChunk.fd`")
return self.get_fwd_ptr()

@property
def bck(self) -> int:
warn(f"[Obsolete] `GlibcChunk.bck` is deprecated, use `GlibcChunk.bk`")
return self.get_bkw_ptr()
# endif obsolete functions


def has_p_bit(self) -> bool:
return bool(self.flags & GlibcChunk.ChunkFlags.PREV_INUSE)

Expand Down Expand Up @@ -1680,6 +1650,34 @@ def psprint(self) -> str:
return "\n".join(msg) + "\n"


class GlibcFastChunk(GlibcChunk):

@property
def fd(self) -> int:
assert(gef and gef.libc.version)
if gef.libc.version < (2, 32):
return self._chunk.fd
return self.reveal_ptr(self.data_address)

def protect_ptr(self, pos: int, pointer: int) -> int:
"""https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L339"""
assert(gef and gef.libc.version)
if gef.libc.version < (2, 32):
return pointer
return (pos >> 12) ^ pointer

def reveal_ptr(self, pointer: int) -> int:
"""https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L341"""
assert(gef and gef.libc.version)
if gef.libc.version < (2, 32):
return pointer
return gef.memory.read_integer(pointer) ^ (pointer >> 12)

class GlibcTcacheChunk(GlibcFastChunk):

pass
theguy147 marked this conversation as resolved.
Show resolved Hide resolved


@lru_cache()
def get_libc_version() -> Tuple[int, ...]:
sections = gef.memory.maps
Expand Down Expand Up @@ -1753,8 +1751,8 @@ def show_last_exception() -> None:
def _show_code_line(fname: str, idx: int) -> str:
fname = os.path.expanduser(os.path.expandvars(fname))
with open(fname, "r") as f:
__data = f.readlines()
return __data[idx - 1] if 0 < idx < len(__data) else ""
_data = f.readlines()
return _data[idx - 1] if 0 < idx < len(_data) else ""

gef_print("")
exc_type, exc_value, exc_traceback = sys.exc_info()
Expand Down Expand Up @@ -6203,22 +6201,23 @@ def pprint_bin(arena_addr: str, index: int, _type: str = "") -> int:
warn("Invalid backward and forward bin pointers(fw==bk==NULL)")
return -1

if gef.libc.version >= (2, 34):
if index >= 1:
previous_bin_address = arena.bin_at(index-1)
if previous_bin_address == fd:
return 0
if _type == "tcache":
Copy link
Owner

Choose a reason for hiding this comment

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

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we want specific cases such as GlibcTcacheChunk & GlibcFastChunk, it is required to modify the private attribute to be public.

chunkClass = GlibcTcacheChunk
elif _type == "fast":
chunkClass = GlibcFastChunk
else:
chunkClass = GlibcChunk

nb_chunk = 0
head = GlibcChunk(bk, from_base=True).fd
head = chunkClass(bk, from_base=True).fd
if fd == head:
return nb_chunk

ok(f"{_type}bins[{index:d}]: fw={fd:#x}, bk={bk:#x}")

m = []
while fd != head:
chunk = GlibcChunk(fd, from_base=True)
chunk = chunkClass(fd, from_base=True)
m.append(f"{RIGHT_ARROW} {chunk!s}")
fd = chunk.fd
nb_chunk += 1
Expand Down Expand Up @@ -6303,7 +6302,7 @@ def do_invoke(self, argv: List[str]) -> None:
if next_chunk == 0:
break

chunk = GlibcChunk(next_chunk)
chunk = GlibcTcacheChunk(next_chunk)
except gdb.MemoryError:
msg.append(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]")
break
Expand Down Expand Up @@ -6360,13 +6359,13 @@ def check_thread_ids(tids: List[int]) -> List[int]:
return list(valid_tids)

@staticmethod
def tcachebin(tcache_base: int, i: int) -> Tuple[Optional[GlibcChunk], int]:
def tcachebin(tcache_base: int, i: int) -> Tuple[Optional[GlibcTcacheChunk], int]:
"""Return the head chunk in tcache[i] and the number of chunks in the bin."""
if i >= GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS:
err("Incorrect index value, index value must be between 0 and {}-1, given {}".format(GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS, i))
return None, 0

tcache_chunk = GlibcChunk(tcache_base)
tcache_chunk = GlibcTcacheChunk(tcache_base)

# Glibc changed the size of the tcache in version 2.30; this fix has
# been backported inconsistently between distributions. We detect the
Expand All @@ -6389,7 +6388,7 @@ def tcachebin(tcache_base: int, i: int) -> Tuple[Optional[GlibcChunk], int]:
count = u16(gef.memory.read(tcache_base + tcache_count_size*i, 2))

chunk = dereference(tcache_base + tcache_count_size*GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS + i*gef.arch.ptrsize)
chunk = GlibcChunk(int(chunk)) if chunk else None
chunk = GlibcTcacheChunk(int(chunk)) if chunk else None
return chunk, count


Expand Down Expand Up @@ -6451,7 +6450,7 @@ def fastbin_index(sz: int) -> int:
if next_chunk == 0:
break

chunk = GlibcChunk(next_chunk, from_base=True)
chunk = GlibcFastChunk(next_chunk, from_base=True)
except gdb.MemoryError:
gef_print(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", end="")
break
Expand Down