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'
printhex(very_understandable_function())
return1
if__name__=='__main__':
sys.exit(main(len(sys.argv),sys.argv))