Last active
November 3, 2022 23:50
-
-
Save ex0dus-0x/45634e9e13105409b50a24aeb6698337 to your computer and use it in GitHub Desktop.
Unpacking memfd malware with Qiling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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