Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making x64 hello sample position independent #26

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bindiff
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

import binascii
import difflib
Expand Down Expand Up @@ -40,7 +40,7 @@ if __name__ == '__main__':
backtrack = True

if len(sys.argv) != 3:
print 'Usage: %s [--backtrack] <orig> <new>' % sys.argv[0]
print('Usage: %s [--backtrack] <orig> <new>' % sys.argv[0])
sys.exit(1)

bina, binb = sys.argv[1:3]
Expand Down
59 changes: 54 additions & 5 deletions core/arch.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import binascii
import re
from capstone import *
from keystone import *

"""
from keystone import *
ks = Ks(KS_ARCH_X86, KS_MODE_64)


"""
class Arch:
def __init__(self):
self.cs = Cs(*self._cs)
Expand All @@ -12,17 +19,54 @@ def asm(self, asm, addr=0, att_syntax=False):
if not asm:
return ''
# asm start label for use with relative offsets
asm = '_PKST_:;' + asm
asm = '_PKST_:;\n' + asm

saved = self.ks.syntax
if att_syntax:
self.ks.syntax = KS_OPT_SYNTAX_ATT
tmp, _ = self.ks.asm(asm, addr=addr)

#Keystone doesn't support this instruction
asm = asm.replace('endbr64', '')

newasm = ''
for line in asm.split('\n'):
if '.long' in line:
x = line.split('\t')
if '-' in x[1]:
vals = x[1].split('-')
new_line = f'{x[0]}\t 0x{vals[0].strip()} - 0x{vals[1].strip()} \n'
newasm += new_line
continue
if re.match(r'^\d+:', line):
continue
newasm += f'{line}\n'

#print('------------')
#import keystone
#for line in newasm.split('\n'):
# print(f'checking line: {line}')
# try:
# tmp, _ = self.ks.asm(line)
# print(tmp)
# except keystone.keystone.KsError as e:
# print(e)
#print(newasm)

# Problematic instructions:
# https://github.com/keystone-engine/keystone/issues/546
# leal -48(%rax,%rdx), %eax
# movb (%rcx,%rdx), %dl
tmp, _ = self.ks.asm(newasm, addr=addr)
self.ks.syntax = saved
return ''.join(map(chr, tmp))
return ''.join(map(chr, tmp)).encode('latin')

def dis(self, raw, addr=0):
return list(self.cs.disasm(str(raw), addr))
if isinstance(raw, bytearray):
return list(self.cs.disasm(raw, addr))
elif isinstance(raw, str):
return list(self.cs.disasm((raw.encode()), addr))
else:
return list(self.cs.disasm(raw, addr))

def jmp(self, dst):
raise NotImplementedError
Expand All @@ -41,7 +85,12 @@ class x86(Arch):
_ks = KS_ARCH_X86, KS_MODE_32

def call(self, dst): return 'call 0x%x;' % dst
def jmp(self, dst): return 'jmp 0x%x;' % dst
def jmp(self, dst):
print(f'debugging jmp: dst:{dst}')
if isinstance(dst, str):
return f'jmp {dst}'
else:
return 'jmp 0x%x;' % dst

def ret(self): return 'ret;'
def nop(self): return 'nop;'
Expand Down
8 changes: 4 additions & 4 deletions core/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def next_alloc(self, target='patch'):
def alloc(self, size, target='patch'):
ph = self._seg(target)
tmp = self.next_alloc(target)
ph.data += '\0' * size
ph.data += b'\0' * size
ph.memsz += size
ph.filesz += size
return tmp
Expand All @@ -87,12 +87,12 @@ def onasm(self, cb):
def save(self, path):
self.nxpatch.flags &= ~1

print '[+] Saving binary to: %s' % path
print('[+] Saving binary to: %s' % path)
# hooking the entry point is a special case that generates a more efficient call table
if self.entry_hooks:
with self.collect() as pt:
# call each hook addr then jump to original entry point
calls = map(pt.arch.call, self.entry_hooks) + [pt.arch.jmp(pt.entry)]
calls = list(map(pt.arch.call, self.entry_hooks)) + [pt.arch.jmp(pt.entry)]
addr = pt.inject(asm=';'.join(calls), internal=True)
pt.entry = addr

Expand All @@ -105,4 +105,4 @@ def save(self, path):
self.elf.progs.remove(prog)

self.elf.save(path)
os.chmod(path, 0755)
os.chmod(path, 0o755)
15 changes: 7 additions & 8 deletions core/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def clean(asm):
elif section.startswith(('.text', '__TEXT')):
cur = text
else:
print 'unknown section', section
print('unknown section', section)
continue

if line.startswith('.text'):
Expand Down Expand Up @@ -78,7 +78,7 @@ def clean(asm):
'''
if line.startswith('.') and not line.endswith(':'):
if not line.startswith(('.long', '.byte')):
print line
print(line)
'''

cur.append(line)
Expand All @@ -92,18 +92,17 @@ def compile(code, linker, syms=()):
if compiler_version is None:
compiler_version = subprocess.check_output(['gcc', '--version'])

if 'gcc' in compiler_version and not 'clang' in compiler_version:
if b'gcc' in compiler_version and not b'clang' in compiler_version:
cflags += ['-fleading-underscore', '-fno-toplevel-reorder']

cflags += linker.cflags
code = linker.pre(code, syms=syms)
p = subprocess.Popen(['gcc', '-xc', '-S', '-o-', '-'] + cflags, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
asm, err = p.communicate(code)
if 'error:' in err.lower():
asm, err = p.communicate(code.encode('latin'))
if b'error:' in err.lower():
raise BuildError(err)
elif err:
print err

asm = linker.post(asm, syms=syms)
print(err)
asm = linker.post(asm.decode(), syms=syms)
asm = clean(asm)
return asm
38 changes: 25 additions & 13 deletions core/context.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import codecs
import sys

import capstone
import binascii

Expand Down Expand Up @@ -76,14 +79,14 @@ def info(self, *args, **kwargs):
if self.current_func:
if self.func_printed != self.current_func:
if self.func_printed is not None:
print
print()
func = self.current_func
print indent + '[FUNC] @0x%x-0x%x' % (func.addr, func.addr + func.size)
print(indent + '[FUNC] @0x%x-0x%x' % (func.addr, func.addr + func.size))
self.func_printed = self.current_func
indent += ' '
if kwargs.get('prefix'):
indent += kwargs['prefix'] + ' '
print indent + line
print(indent + line)

dis = kwargs.get('dis', None)
if dis:
Expand All @@ -109,25 +112,25 @@ def pdis(self, dis):

out = []
nop_start = 0
nop_bytes = ''
nop_bytes = b''
nops = 0
just = max(len(i.bytes) for i in dis)

pnop = lambda: ('0x%x: %s nop (x%d)' % (nop_start, binascii.hexlify(nop_bytes).ljust(just * 2), nops))
pnop = lambda: ('0x%x: %s nop (x%d)' % (nop_start, binascii.hexlify(nop_bytes).ljust(just * 2).decode(), nops))

for i in dis:
if i.mnemonic == 'nop':
if not nops:
nop_start = i.address
nop_bytes += str(i.bytes)
nop_bytes += bytes(i.bytes)
nops += 1
else:
if nops:
out.append(pnop())
nops = 0
nop_bytes = ''
data = binascii.hexlify(i.bytes).ljust(just * 2)
out.append('0x%x: %s %s %s' % (i.address, data, i.mnemonic, i.op_str))
out.append('0x%x: %s %s %s' % (i.address, data.decode(), i.mnemonic, i.op_str))
if nops:
out.append(pnop())
return '\n'.join(out)
Expand Down Expand Up @@ -202,10 +205,13 @@ def hook(self, src, dst, first=False, noentry=False):

# our injected code is guaranteed to be sequential and unaligned
# so we can inject twice and call the first one
evicted = ''
evicted = b''
# eh we'll just trust that a call won't be anywhere near 64 bytes
ins = self.dis(src)
for ins in ins:
instructs = self.dis(src)
for ins in instructs:
for b in ins.bytes:
sys.stdout.write(f'{hex(b)[1:]} ')
sys.stdout.flush()
evicted += ins.bytes
if len(evicted) >= len(call):
break
Expand All @@ -227,7 +233,7 @@ def hook(self, src, dst, first=False, noentry=False):

emptyjmp = self.asm(self.arch.jmp(self.binary.next_alloc()), addr=src)
jmpoff = src + len(evicted)
jmpevict = str(self.elf.read(jmpoff, len(emptyjmp)))
jmpevict = self.elf.read(jmpoff, len(emptyjmp))

stage0 = evicted + jmpevict
# TODO: self.alloc()?
Expand Down Expand Up @@ -263,7 +269,7 @@ def _lint(self, addr, raw, typ, is_asm=False):
if typ == 'asm' or is_asm:
dis = self.arch.dis(raw, addr=addr)
for ins in dis:
if ins.bytes == 'ebfe'.decode('hex'):
if ins.bytes == codecs.decode('ebfe','hex'):
self.warn('JMP 0 emitted!')

def _compile(self, addr, **kwargs):
Expand Down Expand Up @@ -319,7 +325,13 @@ def inject(self, **kwargs):
if typ == 'asm' or is_asm:
self.debug(dis=self.arch.dis(raw, addr=addr))
else:
self.debug(binascii.hexlify(raw))
if isinstance(raw, str):
self.debug(binascii.hexlify(raw.encode('utf-8')))
elif isinstance(raw, bytes):
self.debug(str(raw))
else:
raise RuntimeError('unsupported type')


addr = self.binary.alloc(len(raw), target=target)
if mark_func:
Expand Down
8 changes: 4 additions & 4 deletions core/linker.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from contextlib import contextmanager

import compiler
Expand Down Expand Up @@ -37,10 +38,9 @@ def inject(self, linker, sym):
else:
pt.info('[LINK] %s' % sym)
asm = compiler.compile(self.source, linker, syms=self.syms.keys())

table = '\n'.join([pt.arch.jmp('_' + sym) for sym in self.syms.keys()])
sep = 'PATCHKITJMPTABLE'
asm += ('\n.ascii "%s"\n__JMPTABLE__:\n' % sep) + table
sep = b'PATCHKITJMPTABLE'
asm += ('\n.ascii "%s"\n__JMPTABLE__:\n' % sep.decode()) + table
addr = pt.binary.next_alloc('link')
raw = pt.asm(asm, addr=addr, att_syntax=True)
raw, jmps = raw.rsplit(sep, 1)
Expand Down Expand Up @@ -76,7 +76,7 @@ def declare(self, symbols=None, source='', headers=''):
if symbols:
for sym, desc in symbols.items():
if sym in self.syms:
print 'Warning: duplicate symbol (%s)' % sym
print('Warning: duplicate symbol (%s)' % sym)
self.syms[sym] = (desc, decl)

@staticmethod
Expand Down
3 changes: 2 additions & 1 deletion core/patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def add(self, path):

def debug(self, *args):
if not self.silent:
print >>sys.stderr, ' '.join(map(str, args))
print(' '.join(map(str, args)))
#sys.stderr.write(' '.join(map(str, args)))

def patch(self):
cwd = os.getcwd()
Expand Down
23 changes: 12 additions & 11 deletions deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ echo " https://github.com/unicorn-engine/unicorn/blob/master/docs/COMPILE-NIX.md
echo
echo "If you're on Ubuntu, you want to do this first:"
echo " sudo apt-get update"
echo " sudo apt-get install python-pip build-essential git cmake python-dev libglib2.0-dev"
echo " sudo apt-get install python3-pip build-essential git cmake python-dev libglib2.0-dev"
echo
echo "If you're on a Mac, do this first:"
echo " brew install pkg-config glib cmake"
Expand All @@ -23,6 +23,11 @@ build="$cwd/build"
mkdir build &>/dev/null
set -e

echo "[*] Building Unicorn"
cd "$build"
git clone https://github.com/unicorn-engine/unicorn.git
cd unicorn && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && sudo make -j2

echo "[*] Building Keystone"
cd "$build"
git clone https://github.com/keystone-engine/keystone.git
Expand All @@ -36,26 +41,22 @@ git clone https://github.com/aquynh/capstone.git
cd capstone && make -j2
echo

echo "[*] Building Unicorn"
cd "$build"
git clone https://github.com/unicorn-engine/unicorn.git
cd unicorn && ./make.sh

echo
echo "[*] Installing projects and Python bindings (using sudo)"
cd "$build/unicorn/build" && sudo make install
cd "$build/unicorn/bindings/python" && sudo make install3

cd "$build/keystone/build" && sudo make install
cd "$build/keystone/bindings/python" && sudo make install
cd "$build/keystone/bindings/python" && sudo make install3

cd "$build/capstone" && sudo make install
cd "$build/capstone/bindings/python" && sudo make install
cd "$build/capstone/bindings/python" && sudo make install3

cd "$build/unicorn" && sudo ./make.sh install
cd "$build/unicorn/bindings/python" && sudo make install

which ldconfig &>/dev/null && sudo ldconfig

echo
echo "All done!"
echo
echo -n "Testing Python import: "
python -c "import capstone, keystone, unicorn; capstone.CS_ARCH_X86, unicorn.UC_ARCH_X86, keystone.KS_ARCH_X86; print 'works.'"
python3 -c "import capstone, keystone, unicorn; capstone.CS_ARCH_X86, unicorn.UC_ARCH_X86, keystone.KS_ARCH_X86; print('works.')"
4 changes: 2 additions & 2 deletions explore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

import binascii
import difflib
Expand All @@ -15,7 +15,7 @@ if __name__ == '__main__':
backtrack = True

if len(sys.argv) != 2:
print 'Usage: %s [--backtrack] <binary>' % sys.argv[0]
print('Usage: %s [--backtrack] <binary>' % sys.argv[0])
sys.exit(1)

bina = sys.argv[1]
Expand Down
Loading