Last active
March 12, 2023 19:29
-
-
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].
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 | |
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