From d6f1f126697783521cf7b86a454e40730aaab5da Mon Sep 17 00:00:00 2001 From: JoYo <> Date: Wed, 20 Feb 2019 23:32:34 +0000 Subject: [PATCH] scrap growth one nop at a time, store disassembly --- Dockerfile | 4 ++-- sins/__init__.py | 2 +- sins/disassemble.py | 13 +++++++++++++ sins/mutation.py | 35 ++++++++++++++++++++++++++--------- sins/orm.py | 21 ++++++++++++++++----- sins/run.py | 45 ++++++++++++++++++++++----------------------- 6 files changed, 80 insertions(+), 40 deletions(-) create mode 100644 sins/disassemble.py diff --git a/Dockerfile b/Dockerfile index b9a6664..f11d708 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,5 +2,5 @@ FROM ubuntu:bionic ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y \ - python3-sqlalchemy \ - yasm + python3-capstone \ + python3-sqlalchemy diff --git a/sins/__init__.py b/sins/__init__.py index 2b0ce9d..0efa149 100644 --- a/sins/__init__.py +++ b/sins/__init__.py @@ -1,4 +1,4 @@ #!/usr/bin/env python3 from .run import sins -from .mutation import generation, flip, seed_shell +from .mutation import generation, mutate from .orm import db_config, ScrapNode diff --git a/sins/disassemble.py b/sins/disassemble.py new file mode 100644 index 0000000..0e0d085 --- /dev/null +++ b/sins/disassemble.py @@ -0,0 +1,13 @@ +#! /usr/bin/env python3 +from capstone import Cs, CS_ARCH_X86, CS_MODE_64 +import json + +capstone = Cs(CS_ARCH_X86, CS_MODE_64) + +def disasm(shellcode: bytes)->str: + opcodes = list() + + for opcode in capstone.disasm(shellcode, 0): + opcodes.append([opcode.mnemonic, opcode.op_str]) + + return opcodes diff --git a/sins/mutation.py b/sins/mutation.py index 7c3d290..23a9e97 100644 --- a/sins/mutation.py +++ b/sins/mutation.py @@ -4,14 +4,6 @@ from random import randint import ctypes import mmap -template_shell = b''.join([ - b'\x55', # push rbp - b'\x48\x89\xe5', # mov rbp,rsp - b'\x48\x89\x7d\xf8', # mov QWORD [rbp-0x8],rdi - b'\x48\x8b\x45\xf8', # mov rax,QWORD [rbp-0x8] - b'\x5d', # pop rbp - b'\xc3']) # ret - seed_shell = b''.join([ b'\x55', b'\x48\x89\xe5', @@ -22,8 +14,19 @@ seed_shell = b''.join([ b'\x5d', b'\xc3']) +# use template to declutter output, todo +seed_shell = b''.join([ + b'\x55', # push rbp + b'\x48\x89\xe5', # mov rbp,rsp + b'\x90' * 8, # nop + b'\x48\x89\x7d\xf8', # mov QWORD [rbp-0x8],rdi + b'\x90' * 8, # nop + b'\x48\x8b\x45\xf8', # mov rax,QWORD [rbp-0x8] + b'\x5d', # pop rbp + b'\xc3']) # ret -def flip(shellcode: bytes): + +def mutate(shellcode: bytes): shellcode = bytearray(shellcode) offset = randint(0, len(shellcode) - 1) flip = randint(0, 255) @@ -47,3 +50,17 @@ def generation(queue: Queue, shellcode: bytes): result = function(shellcode_len) queue.put(result) + + +def growth(*, shellcode: bytes, length: int) -> bytes: + shellcode = bytearray(shellcode) + + # slow growth and stop shrinking + if length > len(shellcode): + growth = 1 + else: + growth = 0 + + shellcode = shellcode + (b'\x90' * growth) + + return bytes(shellcode) diff --git a/sins/orm.py b/sins/orm.py index e6a3fdd..d27a071 100644 --- a/sins/orm.py +++ b/sins/orm.py @@ -6,11 +6,11 @@ from sqlalchemy import LargeBinary, Column, ForeignKey, Integer, String, DateTim from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import Session, relationship, backref from sqlalchemy.orm.collections import attribute_mapped_collection -import logging +import json + +from .disassemble import disasm -logger = logging.getLogger('sins') now = '{0:%Y%m%dT%H%M%S}'.format(datetime.utcnow()) - Base = declarative_base() @@ -29,7 +29,7 @@ class ScrapNode(Base): mtime = Column(DateTime, onupdate=datetime.utcnow) parent_id = Column(Integer, ForeignKey(id)) checksum = Column(String) - stdout = Column(String) + disasm = Column(String) image = Column(LargeBinary) children = relationship( @@ -43,16 +43,18 @@ class ScrapNode(Base): self.image = child self.length = len(child) self.sha1sum + self.disasm = disasm(child) def __repr__(self): values = { 'checksum': self.checksum, 'length': self.length, + 'disasm': self.disasm, 'parent_id': self.parent_id, 'id': self.id, } - return str(values) + return json.dumps(values, indent=1) @property def sha1sum(self): @@ -64,3 +66,12 @@ class ScrapNode(Base): self.checksum = checksum.hexdigest() return self.checksum + + +def disasm(shellcode: bytes) -> str: + opcodes = list() + + for opcode in capstone.disasm(shellcode, 0): + opcodes += f'{opcode.mnemonic} {opcode.op_str}\n' + + return opcodes diff --git a/sins/run.py b/sins/run.py index 2a5bbb4..1e50b86 100755 --- a/sins/run.py +++ b/sins/run.py @@ -8,7 +8,7 @@ from sqlalchemy import exists, desc from tempfile import TemporaryDirectory import logging -from .mutation import generation, flip, seed_shell +from .mutation import generation, mutate, seed_shell, growth from .orm import db_config, ScrapNode @@ -48,18 +48,6 @@ def sins(): logger.info(now) - seed_data = seed_shell - - if args.seed: - seed = Path(args.seed) - - with seed.open('rb') as seed_file: - seed_data = seed_file.read() - - seed = ScrapNode(child=seed_data) - - logger.debug(f'seed:\n{seed}') - if args.output: db_path = Path(f'{args.output}/sins.sqlite') else: @@ -68,38 +56,48 @@ def sins(): session = db_config(db_path) logger.info(f'db_path: {db_path}') + recent = session.query(ScrapNode).order_by(desc('ctime')).first() if args.seed: + seed_path = Path(args.seed) + + with seed_path.open('rb') as seed_file: + seed_data = seed_file.read() + + seed = ScrapNode(child=seed_data) + exists = session.query(ScrapNode).filter( ScrapNode.checksum == seed.checksum) if exists: seed = exists[0] - else: session.add(seed) session.commit() + logger.debug(f'args.seed:\n{seed}') + elif recent: + seed = recent + logger.debug(f'recent:\n{seed}') else: - recent = session.query(ScrapNode).order_by(desc('ctime')).first() - - if recent: - logger.debug(f'recent:\n{recent}') - seed = recent + seed = ScrapNode(child=seed_shell) + session.add(seed) + session.commit() + logger.debug(f'seed_shell:\n{seed}') parent = seed - queue = Queue() while True: lineage = 0 - scrap = flip(parent.image) while lineage < args.lineage: + scrap = mutate(parent.image) logger.debug(f'lineage: {lineage}') result = None proc = Process(target=generation, args=(queue, scrap)) proc.start() + try: result = queue.get(timeout=1) except Empty: @@ -110,10 +108,11 @@ def sins(): lineage += 1 continue + scrap = growth(shellcode=scrap, length=result) + parent = ScrapNode(child=scrap, parent_id=parent.id) - parent.length = result session.add(parent) session.commit() + logger.info(f'scrap:\n{parent}') lineage = 0 - scrap = flip(parent.image)