Skip to content

Instantly share code, notes, and snippets.

@ex0dus-0x
Last active November 3, 2022 23:50
Show Gist options
  • Save ex0dus-0x/45634e9e13105409b50a24aeb6698337 to your computer and use it in GitHub Desktop.
Save ex0dus-0x/45634e9e13105409b50a24aeb6698337 to your computer and use it in GitHub Desktop.
Unpacking memfd malware with Qiling
#!/usr/bin/env python3
"""
memfd_unpack.py
AUTHOR
Alan <ex0dus-0x>
DESCRIPTION
Using Qiling to unpack and decompress a sample being loaded
and executed in-memory.
"""
import os
import sys
import shutil
from qiling import *
from qiling.const import QL_VERBOSE
# arbitrary file descriptor number to return when we emulate `memfd_create`
MEMFD = 123
# path to rootfs for storing dependencies and executables
ROOTFS = "rootfs"
def memfd_create_hook(ql, name, flags, *args, **kwargs):
""" Hooks memfd_create, parse name and return arbitrary file descriptor """
buf = ql.mem.string(name)
ql.log.info(f"memfd_create called, anonymous file called {buf} created")
return MEMFD
def write_hook(ql, write_fd, write_buf, write_count, *args, **kwargs):
""" Instrument write only after in-memory fd is instantiated """
regreturn = 0
buf = ql.mem.read(write_buf, write_count)
# stop write to stdout or stderr and output
if (write_fd == 0x1) or (write_fd == 0x2):
ql.log.error(f"Program error: {str(bytes(buf))}")
sys.exit(1)
# parse out executable if writing to our anonymous fd.
elif (write_fd == MEMFD):
# confirm ELF magic number
if bytes(buf[0:4]) != b'\x7fELF':
ql.log.error(f"Not a valid ELF executable being read!")
sys.exit(1)
ql.log.info("Found valid ELF! Unpacking to disk at `unpacked.elf`.")
with open("unpacked.elf", "wb") as fd:
fd.write(buf)
# write to buffer normally if not with our file descriptor
else:
ql.os.fd[write_fd].write(buf)
regreturn = write_count
return regreturn
def execve_hook(ql, fd, pathname, argv, envp, flags, *args, **kwargs):
""" Prevent execve from running and halt """
pathname = ql.mem.string(pathname)
ql.log.info(f"Stopping execution of anonymous file")
#ql.emu_stop()
# doesn't seem like ql.emu_stop() works at halting execution, so we'll call exit
sys.exit(0)
def main():
if len(sys.argv) != 2:
print("Provide path to packed executable.")
sys.exit(1)
path = sys.argv[1]
if not os.path.exists(path):
print("Binary does not exist forunpacking.")
sys.exit(1)
# the path we provide needs to also reflect inside in rootfs, so make a copy
shutil.copyfile(path, os.path.join(ROOTFS, path))
# instantiate qiling and hook syscalls with callbacks
ql = Qiling([path], ROOTFS, stdout=sys.stdout, multithread=True)
ql.set_syscall(0x1, write_hook)
ql.set_syscall(0x13f, memfd_create_hook)
ql.set_syscall(0x142, execve_hook)
ql.run()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment