Quantcast
Channel: Hacker News 50
Viewing all articles
Browse latest Browse all 9433

stuffz/Python's internals/wildfire.py at master · 0vercl0k/stuffz · GitHub

$
0
0

Comments:"stuffz/Python's internals/wildfire.py at master · 0vercl0k/stuffz · GitHub"

URL:https://github.com/0vercl0k/stuffz/blob/master/Python%27s%20internals/wildfire.py


#!/usr/bin/env python

# -*- coding: utf-8 -*-

#

# wildfire.py - Self-modifying Python bytecode: w.i.l.d.f.i.r.e

# Copyright (C) 2013 Axel "0vercl0k" Souchet - http://www.twitter.com/0vercl0k

#

# This program is free software: you can redistribute it and/or modify

# it under the terms of the GNU General Public License as published by

# the Free Software Foundation, either version 3 of the License, or

# (at your option) any later version.

#

# This program is distributed in the hope that it will be useful,

# but WITHOUT ANY WARRANTY; without even the implied warranty of

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

# GNU General Public License for more details.

#

# You should have received a copy of the GNU General Public License

# along with this program. If not, see <http://www.gnu.org/licenses/>.

#

# Debugging tips:

# Bytecode fetching:

# .text:1E011329 fetch_opcode: ; CODE XREF: PyEval_EvalFrameEx+47CA6j

# .text:1E011329 ; PyEval_EvalFrameEx+47CB0j ...

# .text:1E011329 0F B6 06 movzx eax, byte ptr [esi] ; Here is the bytecode and the current instruction

# .text:1E01132C 46 inc esi

# .text:1E01132D 83 F8 5A cmp eax, 5Ah ; test if the bytecode got an argument or not

# .text:1E011330 89 44 24 18 mov [esp+98h+var_80], eax

# .text:1E011334 7C 4F jl short bytecode_without_argument

# .text:1E011336 0F B6 5E 01 movzx ebx, byte ptr [esi+1] ; Take the two bytes after the instruction,

# .text:1E011336 ; and make a short value (in ebx)

# .text:1E01133A 0F B6 0E movzx ecx, byte ptr [esi]

# .text:1E01133D 83 C6 02 add esi, 2

# .text:1E011340 C1 E3 08 shl ebx, 8

# .text:1E011343 03 D9 add ebx, ecx

# End-of the VM's stack at [ESP-0xC]

# WOOOOTZ it works!

# Windows, Python 2.6.6 x86

# D:\python very_understandable_function.Py266.pyc

# Hello, Windows-7-6.1.7601-SP1 (32bit)

# I like doing stuff with number: 10

# 31337

# 31338

# 31339

# [...]

# dont care!

# Linux, Python 2.6.6 x86

# overclok@theokoles:~/tmp$ python2.6 very_understandable_function.Py266.pyc

# Hello, Linux-2.6.32-5-686-i686-with-debian-6.0 (32bit)

# I like doing stuff with number: 10

# 31337

# 31338

# 31339

# [...]

# dont care!

# Linux, Python 2.6.6 x64

# overclok@spartacus:/tmp$ python2.6 very_understandable_function.Py266.pyc

# Hello, Linux-2.6.32-5-xen-amd64-x86_64-with-debian-6.0.2 (64bit)

# I like doing stuff with number: 10

# 31337

# 31338

# 31339

# [...]

# dont care!

importsys

importtypes

importstring

importdis

importrandom

importmarshal

importstruct

importplatform

frompy_compileimportMAGIC

importopcode

defvery_understandable_function():

    defget_eleet():

        return31337

    importplatform

    print'Hello, %s (%s)'%(platform.platform(),platform.architecture()[0])

    r=10

    print'I like doing stuff with number: %r'%(r%42)

    

    foriinrange(r):

        printi+get_eleet()

    if(r%10):

        print'wUuUUt'

    else:

        print'dont care!'

    withopen('success','w')asf:

        f.write('yoooo seems to work bra!')

    return0xdeadbeef

defencrypt(s):

    return''.join(chr((ord(c)+1)&0xff)forcins)

defopcodes_to_bytecode(opcodes):

    """Kind of bytecode compiler \o/"""

    bytecode=''

    forinstrinopcodes:

        ifinstr[0]inopcode.opmap:

            bytecode+=chr(opcode.opmap.get(instr[0]))

            iflen(instr)>1:

                bytecode+=struct.pack('<H',instr[1])

        else:

            bytecode+=instr[0]

    returnbytecode

defcraft_pyc_via_func_object(function_object):

    """Craft directly a .pyc file with your code object inside"""

    varnames=[]

    code_object=function_object.func_code

    c=code_object.co_code

    names=[function_object.__name__]

    consts=[code_object]

    stub_instrs=opcodes_to_bytecode([

        ('LOAD_CONST',consts.index(code_object)),

        ('MAKE_FUNCTION',0),

        ('STORE_NAME',names.index(function_object.__name__)),

        ('LOAD_NAME',names.index(function_object.__name__)),

        ('CALL_FUNCTION',0),

        ('RETURN_VALUE',)

    ])

    stub_object=types.CodeType(

        0,0,1,0,

        stub_instrs,

        tuple(consts),tuple(names),tuple(varnames),

        '','',137,''

    )

    name='%s.Py%s.pyc'%(

        function_object.__name__,

        ''.join(platform.python_version_tuple())

    )

    withopen(name,'wb')asf:

        f.write(MAGIC)

        f.write(struct.pack('<I',0xBA0BAB))

        marshal.dump(stub_object,f)

        f.flush()

defgenerate_random_strings():

    """Generate a random string"""

    charset=map(chr,range(0,0x100))

    return''.join(random.choice(charset)foriinrange(random.randint(10,100)))

deffind_absolute_instr(code,i_init=0,end=None):

    """Find in code the instructions that use absolute reference, and

returns the offsets of those instructions.

Really useful when you want to relocate a code, you just have to patch

the 2bytes with your relocation offset."""

    i=i_init

    absolute_refs=[]

    ifend==None:

        end=len(code)

    whilei<end:

        byte=ord(code[i])

        i+=1

        ifbyte>=opcode.HAVE_ARGUMENT:

            absolute_offset=struct.unpack('<H',code[i:i+2])[0]

            ifbyteinopcode.hasjabs:

                absolute_refs.append(i)

            i+=2

    returnabsolute_refs

classUniqueList(list):

    """Set doens't have a .index method, so here a class doing the job"""

    defappend(self,r):

        ifnotlist.__contains__(self,r):

            list.append(self,r)

    defextend(self,it):

        foriinit:

            self.append(i)

defadd_encryption_layer(f,number_layer):

    """Add number_layer layers to the function f"""

    c=f.func_code

    original_bytecode=c.co_code

    encryption_marker='\xBA\x0B\xBA\xBE'

    names=UniqueList(c.co_names)

    varnames=UniqueList(c.co_varnames)

    consts=UniqueList(c.co_consts)

    decryption_layers=[]

    relocation_offset=0

    absolute_jmp_infos=find_absolute_instr(original_bytecode)

    print' Instructions with absolute offsets found in the original bytecode: %r'%absolute_jmp_infos

    print' Preparing all the decryption layers..'

    for_inrange(number_layer):

        varnames_to_obfuscated_varnames={

            'code':generate_random_strings(),

            'idx_marker':generate_random_strings(),

            'code_to_decrypt':generate_random_strings(),

            'code_decrypted':generate_random_strings(),

            'memmove':generate_random_strings(),

            'c_char':generate_random_strings(),

            'padding_marker':generate_random_strings(),

            'padding_size':generate_random_strings()

        }

        const_to_obfuscated_const={

            'MARKER':generate_random_strings()

        }

        names.extend([

            f.__name__,

            'ctypes',

            'memmove',

            'c_char',

            'func_code',

            'co_code',

            'rfind',

            'id',

            'len',

            'chr',

            'ord',

            'from_address',

            'raw',

            'find'

        ])

        varnames.extend(varnames_to_obfuscated_varnames.values())

        consts.extend([

            const_to_obfuscated_const['MARKER'],

            len(const_to_obfuscated_const['MARKER']),

            -1,

            ('memmove','c_char'),

            '',

            0xff,

            'ABCDEFGH',

            100

        ])

        stub_decrypt_instrs=[

            ('JUMP_FORWARD',len(encryption_marker)),

            (encryption_marker,),

            # from ctypes import memmove, c_char

            ('LOAD_CONST',consts.index(-1)),# __import__ level argument

            ('LOAD_CONST',consts.index(('memmove','c_char'))),# __import__ fromlist argument

            ('IMPORT_NAME',names.index('ctypes')),

            ('IMPORT_FROM',names.index('memmove')),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['memmove'])),

            ('IMPORT_FROM',names.index('c_char')),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['c_char'])),

            ('POP_TOP',),

            # padding_marker = 'ABCDEFGH'

            ('LOAD_CONST',consts.index('ABCDEFGH')),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['padding_marker'])),

            # padding_size = (c_char * 100).from_address(id(padding_marker)).raw.find(padding_marker)

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['c_char'])),

            ('LOAD_CONST',consts.index(100)),

            ('BINARY_MULTIPLY',),

            ('LOAD_ATTR',names.index('from_address')),

            ('LOAD_GLOBAL',names.index('id')),

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['padding_marker'])),

            ('CALL_FUNCTION',1),

            ('CALL_FUNCTION',1),

            ('LOAD_ATTR',names.index('raw')),

            ('LOAD_ATTR',names.index('find')),

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['padding_marker'])),

            ('CALL_FUNCTION',1),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['padding_size'])),

            # code = decryption_layer.func_code.co_code

            ('LOAD_GLOBAL',names.index(f.__name__)),

            ('LOAD_ATTR',names.index('func_code')),

            ('LOAD_ATTR',names.index('co_code')),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['code'])),

            # idx_marker = code.rfind('MARKER')

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['code'])),

            ('LOAD_ATTR',names.index('rfind')),

            ('LOAD_CONST',consts.index(const_to_obfuscated_const['MARKER'])),

            ('CALL_FUNCTION',1),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['idx_marker'])),

            # code_to_decrypt = code[idx_marker + 6 : ]

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['code'])),

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['idx_marker'])),

            ('LOAD_CONST',consts.index(len(const_to_obfuscated_const['MARKER']))),

            ('BINARY_ADD',),

            # Implements TOS = TOS1[TOS:]

            ('SLICE+1',),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['code_to_decrypt'])),

            # code_decrypted = ''

            ('LOAD_CONST',consts.index('')),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['code_decrypted'])),

            

            # for c in code_to_decrypt:

            # code_decrypted += chr((ord(c) - 1) & 0xff)

            ('SETUP_LOOP',1),

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['code_to_decrypt'])),

            ('GET_ITER',),

            

            ('FOR_ITER',33),

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['code_decrypted'])),

            ('ROT_TWO',),

            ('LOAD_GLOBAL',names.index('chr')),

            ('ROT_TWO',),

            # ord(c)

            ('LOAD_GLOBAL',names.index('ord')),

            ('ROT_TWO',),

            ('CALL_FUNCTION',1),

            # + (-1)

            ('LOAD_CONST',consts.index(-1)),

            ('BINARY_ADD',),

            # & 0xff

            ('LOAD_CONST',consts.index(0xff)),

            ('BINARY_AND',),

            # chr()

            ('CALL_FUNCTION',1),

            # code_decrypted += chr()

            ('BINARY_ADD',),

            ('STORE_FAST',varnames.index(varnames_to_obfuscated_varnames['code_decrypted'])),

            ('JUMP_ABSOLUTE',126),

            ('POP_BLOCK',),

            # memmove(id(code) + padding_size + idx_marker + len(marker), code_decrypted, len(code_decrypted))

            # id(code)

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['memmove'])),

            ('LOAD_GLOBAL',names.index('id')),

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['code'])),

            ('CALL_FUNCTION',1),

            # + padding_size

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['padding_size'])),

            ('BINARY_ADD',),

            # + idx_marker

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['idx_marker'])),

            ('BINARY_ADD',),

            # + len(marker)

            ('LOAD_CONST',consts.index(len(const_to_obfuscated_const['MARKER']))),

            ('BINARY_ADD',),

            # Push code_decrypted

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['code_decrypted'])),

            # len(code_decrypted)

            ('LOAD_GLOBAL',names.index('len')),

            ('LOAD_FAST',varnames.index(varnames_to_obfuscated_varnames['code_decrypted'])),

            ('CALL_FUNCTION',1),

            # memmove call!

            ('CALL_FUNCTION',3),

            ('POP_TOP',),

            # Don't forgot to jump over the marker

            ('JUMP_FORWARD',len(const_to_obfuscated_const['MARKER'])),

            (const_to_obfuscated_const['MARKER'],)

        ]

        stub_decrypt_opcodes=opcodes_to_bytecode(stub_decrypt_instrs)

        relocation_offset+=len(stub_decrypt_opcodes)

        decryption_layers.append(bytearray(stub_decrypt_opcodes))

    # First, patch the absolute references in the original bytecode

    # Note: the original_relocated_bytecode is valid only when it will be prepended by the X layerz

    print' Relocate the original bytecode (size of all the stubs: %d bytes)'%relocation_offset

    original_relocated_bytecode=bytearray(original_bytecode)

    forpatch_offsetinabsolute_jmp_infos:

        print' Patching absolute instruction at offset %.8x'%patch_offset

        off=struct.unpack(

            '<H',

            str(original_relocated_bytecode[patch_offset:patch_offset+2])

        )[0]

        off+=relocation_offset

        original_relocated_bytecode[patch_offset:patch_offset+2]=struct.pack('<H',off)

    # Why 7? It's the size of the 2 first instruction of our payload

    # We don't want to desynchronize our "disassembler"

    # Let's find absolute instruction only in the 170 first bytes, we don't want to

    # search stuff in the final marker ;)

    absolute_jmps_stub=find_absolute_instr(

        str(decryption_layers[0]),

        7,

        170

    )

    stub_relocation_offset=0

    forlayerinreversed(decryption_layers):

        forpatch_offsetinabsolute_jmps_stub:

            off=struct.unpack(

                '<H',

                str(layer[patch_offset:patch_offset+2])

            )[0]

            off+=stub_relocation_offset

            layer[patch_offset:patch_offset+2]=struct.pack('<H',off)

        stub_relocation_offset+=len(layer)

    print' Now assemble the layers..'

    

    final_bytecode=str(original_relocated_bytecode)

    forlayerindecryption_layers:

        final_bytecode=str(layer)+encrypt(final_bytecode)

    print' Final payload size: %d'%len(final_bytecode)

    f.func_code=types.CodeType(

        c.co_argcount,len(varnames),max(c.co_stacksize,10),c.co_flags,

        final_bytecode,

        tuple(consts),tuple(names),tuple(varnames),

        c.co_filename,c.co_name,c.co_firstlineno,c.co_lnotab

    )

defmain(argc,argv):

    print'1. Adding the decryption layers...'

    add_encryption_layer(very_understandable_function,200)

    

    print'2. Generation of the .pyc file..'

    craft_pyc_via_func_object(very_understandable_function)

    print'3. Calling the resulting function, to see if it works'

    print

    printhex(very_understandable_function())

    return1

if__name__=='__main__':

    sys.exit(main(len(sys.argv),sys.argv))


Viewing all articles
Browse latest Browse all 9433

Trending Articles