Skip to content

Instantly share code, notes, and snippets.

@arget13
Last active March 12, 2023 19:29
Show Gist options
  • Save arget13/469cffc618918faed697e5d7a2b8faf8 to your computer and use it in GitHub Desktop.
Save arget13/469cffc618918faed697e5d7a2b8faf8 to your computer and use it in GitHub Desktop.
The worst packer in history. Made just for fun, please don't use in a production environment. The arguments are embedded onto the binary, encrypted. The -n option is to not reuse argv[0].
#!/usr/bin/env python3
from os import *
from mmap import PROT_EXEC, PROT_READ
from sys import argv
key = 0xa3
ELF_READ = 4
ELF_WRITE = 2
ELF_EXEC = 1
def create_phdr(e_type, flags, offset, vaddr, filesz, memsz, align):
phdr = b''
phdr += e_type.to_bytes(4, "little") # type
phdr += flags. to_bytes(4, "little") # flags
phdr += offset.to_bytes(8, "little") # off
phdr += vaddr. to_bytes(8, "little") # vaddr
phdr += vaddr. to_bytes(8, "little") # paddr
phdr += filesz.to_bytes(8, "little") # filesz
phdr += memsz. to_bytes(8, "little") # memsz
phdr += align. to_bytes(8, "little") # align
return phdr
def search_text(header):
shoff = int.from_bytes(header[0x28:0x28 + 8], "little")
shentsize = int.from_bytes(header[0x3a:0x3a + 2], "little")
shnum = int.from_bytes(header[0x3c:0x3c + 2], "little")
shstrndx = int.from_bytes(header[0x3e:0x3e + 2], "little")
shdrs_buf = pread(fd, shentsize * shnum, shoff)
shdrs = []
for i in range(shnum):
shdrs.append(shdrs_buf[i * shentsize : (i + 1) * shentsize])
shstrtab_off = int.from_bytes(shdrs[shstrndx][0x18:0x18 + 8], "little")
shstrtab_sz = int.from_bytes(shdrs[shstrndx][0x20:0x20 + 8], "little")
shstrtab = pread(fd, shstrtab_sz, shstrtab_off)
text_shdr = None
for shdr in shdrs:
sh_name = int.from_bytes(shdr[0x0:0x0 + 4], "little")
end_name = shstrtab[sh_name:].find(b"\x00")
name = shstrtab[sh_name:sh_name + end_name]
if(name == b".text"):
text_shdr = shdr
break
return text_shdr
if __name__ == "__main__":
if(len(argv) < 2):
print("Usage: %s [-n] filename [arguments]" % argv[0])
exit()
if(argv[1] == "-n"):
KEEP_ARGV0 = False
del argv[1]
else:
KEEP_ARGV0 = True
fd = open(argv[1], O_RDWR)
args = argv[2:]
for i in range(len(args)): args[i] = bytes(args[i], "utf-8")
header = read(fd, 0x40)
fsize = lseek(fd, 0, SEEK_END)
fsize = (fsize + 0xfff) & 0xfffffffffffff000
entry = int.from_bytes(header[0x18:0x18 + 8], "little")
phoff = int.from_bytes(header[0x20:0x20 + 8], "little")
phentsize = int.from_bytes(header[0x36:0x36 + 2], "little")
phnum = int.from_bytes(header[0x38:0x38 + 2], "little")
# Read and separate all PHDRs
phdrs_buf = pread(fd, phentsize * phnum, phoff)
phdrs = []
for i in range(phnum):
phdrs.append(phdrs_buf[i * phentsize : (i + 1) * phentsize])
# Find first executable segment in the PHDR table
xseg_phdr_idx = None
for i in range(phnum):
if(phdrs[i][0x0:0x0 + 4] != b"\x01\x00\x00\x00"): continue # not PT_LOAD
if(int.from_bytes(phdrs[i][0x4:0x4 + 4], "little") & ELF_EXEC):
xseg_phdr_idx = i
xseg_phdr = phdrs[i]
xseg_off = int.from_bytes(xseg_phdr[0x08:0x08 + 8], "little")
xseg_addr = int.from_bytes(xseg_phdr[0x10:0x10 + 8], "little")
xseg_fsize = int.from_bytes(xseg_phdr[0x20:0x20 + 8], "little")
xseg_memsz = int.from_bytes(xseg_phdr[0x28:0x28 + 8], "little")
break
if(xseg_phdr_idx == None):
print("Error: No executable segment")
exit()
# Damned go binaries have the ELF header within the executable segment.
# In the case of binaries with the ELF header in an X segment we will
# encrypt from .text to the end of the segment
xseg_addr_orig = xseg_addr
if(xseg_off == 0):
text_shdr = search_text(header)
if(text_shdr == None):
print("Error: Could not find .text section.")
xseg_off += 0x1000
xseg_addr += 0x1000
xseg_fsize -= 0x1000
xseg_memsz -= 0x1000
else:
text_addr = int.from_bytes(text_shdr[0x10:0x10 + 8], "little")
text_off = int.from_bytes(text_shdr[0x18:0x18 + 8], "little")
xseg_fsize -= text_off - xseg_off
xseg_memsz -= text_addr - xseg_addr
xseg_off = text_off
xseg_addr = text_addr
# Encrypt the segment and edit PHDR table to make it segment RW
xseg = bytearray(pread(fd, xseg_fsize, xseg_off))
for i in range(xseg_fsize):
xseg[i] ^= key
xseg_phdr = bytearray(xseg_phdr)
xseg_phdr[0x4] = ELF_READ | ELF_WRITE
xseg_phdr = bytes(xseg_phdr)
phdrs[xseg_phdr_idx] = xseg_phdr
# Find segment which contains the ELF header, make it RW and find its vaddr
for i in range(phnum):
if(phdrs[i][0x0:0x0 + 4] != b"\x01\x00\x00\x00"): continue # not PT_LOAD
if(int.from_bytes(phdrs[i][0x8:0x8 + 8], "little") == 0):
ehdr_phdr = phdrs[i]
ehdr_phdr = bytearray(ehdr_phdr)
ehdr_phdr[0x4] = ELF_READ | ELF_WRITE
ehdr_phdr = bytes(ehdr_phdr)
phdrs[i] = ehdr_phdr
ehdr_addr = int.from_bytes(ehdr_phdr[0x10:0x10 + 8], "little")
# Find first address not mapped by any segment
vaddr = 0
for phdr in phdrs:
if(phdr[0x0:0x0 + 4] != b"\x01\x00\x00\x00"): continue # not PT_LOAD
p_vaddr = int.from_bytes(phdr[0x10:0x10 + 8], "little")
memsz = int.from_bytes(phdr[0x28:0x28 + 8], "little")
if(vaddr < (p_vaddr + memsz)):
vaddr = p_vaddr + memsz
# Add new segment at said address to contain new PHDR table and code
vaddr = (vaddr + 0xfff) & 0xfffffffff000
phdr_off = fsize
phdrs.append(create_phdr(1, ELF_READ | ELF_EXEC, phdr_off,
vaddr, 0x1000, 0x1000, 0x1000))
phnum += 1
# Add another new segment to contain arguments
args_addr = vaddr + 0x1000
args_off = phdr_off + 0x1000
phdrs.append(create_phdr(1, ELF_READ | ELF_WRITE, args_off,
args_addr, 0x1000, 0x1000, 0x1000))
phnum += 1
# Edit the PHDR entry of type PT_PHDR to point to new PHDR table
for i in range(phnum):
if(phdrs[i][0x0:0x0 + 4] != b"\x06\x00\x00\x00"): continue # not PT_PHDR
phdr_phdr = create_phdr(6, ELF_READ, phdr_off, vaddr,
phnum * phentsize, phnum * phentsize, 0x8)
phdrs[i] = phdr_phdr
break
code_addr = vaddr + phnum * phentsize
code = b""
# Place base address in r15 in case the binary is PIE, null r15 otherwise
if(header[0x10:0x10 + 2] == b"\x03\x00"): # e_type == ET_DYN (PIE)
code += b"\x4c\x8d\x3d\xf9\xff\xff\xff\x49\x81\xef"
code += code_addr.to_bytes(4, "little")
else: # NO-PIE
code += b"\x4d\x31\xff"
# Fix phoff. Glibc in static binaries uses it when it ought to use AUXV
code += b"\x48\xb8"
code += (ehdr_addr + 0x20).to_bytes(8, "little")
code += b"\x48\xbb"
code += (vaddr + 0x20).to_bytes(8, "little")
code += b"\x48\x29\xc3\x4c\x01\xf8\x48\x89\x18"
# Decrypt executable segment and make it RX
code += b"\x49\x89\xd5\x48\xbf"
code += xseg_addr.to_bytes(8, "little")
code += b"\x4c\x01\xff\xbe"
code += xseg_memsz.to_bytes(4, "little")
code += b"\x48\x89\xfb\x48\x01\xf3\x48\x39\xdf\x73\x08\x80\x37"
code += key.to_bytes(1, "little")
code += b"\x48\xff\xc7\xeb\xf3\x48\xbf"
code += xseg_addr_orig.to_bytes(8, "little")
code += b"\x4c\x01\xff\xba"
code += (PROT_READ | PROT_EXEC).to_bytes(4, "little")
code += b"\x48\x31\xc0\xb0\x0a\x0f\x05"
# Clean old arguments and place ours
if(KEEP_ARGV0):
code += b"\x4c\x8b\x74\x24\x08"
code += b"\x48\x8b\x04\x24\x48\x8d\x64\xc4\x08"
args_code = b''
args_addr_ = args_addr
for arg in args:
args_code_aux = b''
args_code_aux += b"\x48\xb8"
args_code_aux += args_addr_.to_bytes(8, "little")
args_code_aux += b"\x4c\x01\xf8\x50"
args_code = args_code_aux + args_code
args_addr_ += len(arg) + 1
code += args_code
code += b"\xb8"
if(KEEP_ARGV0):
code += (len(args) + 1).to_bytes(4, "little")
code += b"\x41\x56"
else:
code += len(args).to_bytes(4, "little")
code += b"\x50"
# Decrypt arguments
args_len = 0
for arg in args: args_len += len(arg) + 1
code += b"\x48\xbf"
code += args_addr.to_bytes(8, "little")
code += b"\x4c\x01\xff\xbe"
code += args_len.to_bytes(4, "little")
code += b"\x48\x89\xfb\x48\x01\xf3\x48\x39\xdf\x73\x08\x80\x37"
code += key.to_bytes(1, "little")
code += b"\x48\xff\xc7\xeb\xf3"
# Jump to original entrypoint
code += b"\x48\xb8"
code += entry.to_bytes(8, "little")
code += b"\x4c\x01\xf8\x4c\x89\xea\xff\xe0"
# Finally, write changes to file
ftruncate(fd, fsize)
lseek(fd, 0, SEEK_END)
pwrite(fd, phdr_off.to_bytes(8, "little"), 0x20) # Change phoff
pwrite(fd, phnum.to_bytes(2, "little"), 0x38) # Change phnum
pwrite(fd, xseg, xseg_off) # Write encrypted segment
for phdr in phdrs: write(fd, phdr) # Write PHDR table
pwrite(fd, code_addr.to_bytes(8, "little"), 0x18) # Change entrypoint
write(fd, code) # Write our code
# Encrypt and append arguments to the binary
ftruncate(fd, args_off)
fsize = lseek(fd, 0, SEEK_END)
for arg in args:
arg += b"\x00"
arg_xor = b''
for c in arg:
arg_xor += (c ^ key).to_bytes(1, "little")
write(fd, arg_xor)
fsize = lseek(fd, 0, SEEK_END)
ftruncate(fd, (fsize + 0xfff) & 0xfffffffff000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment