├── Peach ├── Utilities │ ├── __init__.py │ ├── Gaia │ │ ├── __init__.py │ │ └── atoms │ │ │ └── gaia_lock_screen.js │ ├── JS │ │ └── Loader │ │ │ └── files.html │ ├── network.py │ └── common.py ├── Transformers │ ├── __init__.py │ ├── Encode │ │ ├── __init__.py │ │ ├── UTF8.py │ │ ├── Hex.py │ │ ├── Base64Decode.py │ │ ├── Base64Encode.py │ │ ├── IPv6StringToOctet.py │ │ ├── HTMLDecode.py │ │ ├── HTMLEncode.py │ │ ├── UTF16.py │ │ ├── IPv4StringToOctet.py │ │ ├── URLEncode.py │ │ ├── IPv4StringToNetworkOctet.py │ │ ├── ASCII7bit.py │ │ ├── SIDStringToBytes.py │ │ ├── WideChar.py │ │ ├── HTMLEncodeAggressive.py │ │ ├── JSEncode.py │ │ ├── UTF16LE.py │ │ ├── UTF16BE.py │ │ ├── PDU.py │ │ ├── NetBiosDecode.py │ │ ├── NetBiosEncode.py │ │ └── HexString.py │ ├── Type │ │ ├── __init__.py │ │ ├── StringToInt.py │ │ ├── StringToFloat.py │ │ ├── IntToHex.py │ │ ├── Pack.py │ │ ├── NumberToString.py │ │ └── Integer.py │ ├── Compression │ │ ├── __init__.py │ │ ├── Bzip2.py │ │ ├── Gzip.py │ │ └── Zlib.py │ ├── Cryptography │ │ ├── __init__.py │ │ ├── SHA1.py │ │ ├── MD5.py │ │ ├── HMAC.py │ │ ├── CVS.py │ │ ├── ASN1.py │ │ └── UnixMD5Crypt.py │ ├── Null.py │ └── Eval.py ├── Fixups │ ├── __init__.py │ ├── numbers.py │ └── strings.py ├── Strategies │ ├── __init__.py │ ├── default.py │ └── random.py ├── MutateStrategies │ └── __init__.py ├── Engine │ ├── __init__.py │ └── defaults.xml ├── Mutators │ ├── __init__.py │ ├── Values.py │ ├── path.py │ └── datatree.py ├── Agent │ ├── __init__.py │ ├── linux.py │ ├── util.py │ ├── gui.py │ └── socketmon.py ├── Publishers │ ├── __init__.py │ ├── remote.py │ ├── irc.py │ ├── sql.py │ ├── httpserver.py │ ├── icmp.py │ ├── dll.py │ ├── stdout.py │ ├── sslayer.py │ ├── com.py │ ├── smtp.py │ └── usb.py ├── Generators │ ├── __init__.py │ ├── null.py │ ├── flipper.py │ ├── unicode.py │ └── repeater.py ├── __init__.py ├── Analyzers │ ├── pit.py │ ├── __init__.py │ ├── stringtoken.py │ └── asn1.py ├── fixup.py ├── analyzer.py ├── mutator.py ├── transformer.py ├── config.py ├── mutatestrategies.py └── strategy.py ├── requirements.txt ├── AUTHORS.md ├── CODE_OF_CONDUCT.md ├── Pits ├── Files │ ├── XML │ │ └── xml.xml │ └── blob.xml └── Targets │ ├── stdout.xml │ ├── console.xml │ └── Firefox │ └── firefox.xml ├── .gitignore └── README.md /Peach/Utilities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Peach/Transformers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Peach/Utilities/Gaia/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Peach/Transformers/Type/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Peach/Transformers/Compression/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Peach/Transformers/Cryptography/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Twisted==14.0.0 2 | lxml==3.3.5 3 | psutil==2.1.1 4 | pyasn1==0.1.7 5 | tlslite==0.4.6 6 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | ## Credits 2 | 3 | #### Development Lead 4 | 5 | * Christoph Diehl cdiehl@mozilla.com 6 | 7 | #### Contributors 8 | 9 | * Michael Eddington 10 | * Jesse Schwarzentruber 11 | * Tyson Smith 12 | * Christian Holler 13 | * Mark Goodwin 14 | * Aditya Murray 15 | -------------------------------------------------------------------------------- /Peach/Fixups/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import checksums 5 | import numbers 6 | import strings 7 | 8 | __all__ = ["checksums", "numbers", "strings"] 9 | -------------------------------------------------------------------------------- /Peach/Strategies/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import default 5 | import random 6 | 7 | __all__ = [ 8 | "default", 9 | "random" 10 | ] 11 | -------------------------------------------------------------------------------- /Peach/MutateStrategies/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import rand 5 | import sequential 6 | 7 | __all__ = [ 8 | "rand", 9 | "sequential" 10 | ] 11 | -------------------------------------------------------------------------------- /Peach/Utilities/JS/Loader/files.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Peach WebSocket Publisher 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Peach/Engine/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | # Circular dependencies were caused by this file! 6 | 7 | #import engine, parser, incoming, dom, state, common 8 | #__all__ = [ "engine", "parser", "incoming", "dom", "state", "common" ] 9 | 10 | # end 11 | -------------------------------------------------------------------------------- /Peach/Transformers/Type/StringToInt.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class StringToInt(Transformer): 8 | """Transform a string into an integer (atoi).""" 9 | 10 | def realEncode(self, data): 11 | return int(data) 12 | -------------------------------------------------------------------------------- /Peach/Transformers/Type/StringToFloat.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class StringToFloat(Transformer): 8 | """Transform a string into an float (atof).""" 9 | 10 | def realEncode(self, data): 11 | return float(data) 12 | -------------------------------------------------------------------------------- /Peach/Mutators/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import string 5 | import datatree 6 | import number 7 | import blob 8 | import array 9 | import size 10 | import path 11 | 12 | __all__ = [ 13 | "string", 14 | "datatree", 15 | "number", 16 | "blob", 17 | "array", 18 | "size", 19 | "path" 20 | ] 21 | -------------------------------------------------------------------------------- /Peach/Transformers/Null.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class Null(Transformer): 8 | """Null transformer. Returns that data unaltered.""" 9 | 10 | def realEncode(self, data): 11 | return data 12 | 13 | def realDecode(self, data): 14 | return data 15 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/UTF8.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class Utf8(Transformer): 8 | """Encode string as UTF-8.""" 9 | 10 | def realEncode(self, data): 11 | return data.encode("utf8") 12 | 13 | def realDecode(self, data): 14 | return str(data.decode("utf8")) 15 | 16 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/Hex.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from binascii import b2a_hex, a2b_hex 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Hex(Transformer): 10 | """Transform a data stream into Hex.""" 11 | 12 | def realEncode(self, data): 13 | return b2a_hex(data) 14 | 15 | def realDecode(self, data): 16 | return a2b_hex(data) 17 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/Base64Decode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import base64 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Base64Decode(Transformer): 10 | """Base64 decode.""" 11 | 12 | def realEncode(self, data): 13 | return base64.decodestring(data) 14 | 15 | def realDecode(self, data): 16 | return base64.encodestring(data).rstrip().replace('\n', '') 17 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/Base64Encode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import base64 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Base64Encode(Transformer): 10 | """Base64 encode.""" 11 | 12 | def realEncode(self, data): 13 | return base64.encodestring(data).rstrip().replace('\n', '') 14 | 15 | def realDecode(self, data): 16 | return base64.decodestring(data) 17 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/IPv6StringToOctet.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Ipv6StringToOctet(Transformer): 10 | """Convert a collen notation IPv6 address into a 4 byte octet 11 | representation. 12 | """ 13 | 14 | def realEncode(self, data): 15 | return pack('BBBBBBBBBBBBBBBB', data.split(':')) 16 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/HTMLDecode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import xml.sax.saxutils 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class HtmlDecode(Transformer): 10 | """Decode HTML encoded string.""" 11 | 12 | def realEncode(self, data): 13 | return xml.sax.saxutils.unescape(data) 14 | 15 | def realEncode(self, data): 16 | return xml.sax.saxutils.escape(data) 17 | -------------------------------------------------------------------------------- /Peach/Agent/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import network 5 | import debugger 6 | import process 7 | import test 8 | import memory 9 | import gui 10 | import socketmon 11 | import osx 12 | import util 13 | 14 | __all__ = [ 15 | "network", 16 | "debugger", 17 | "process", 18 | "test", 19 | "memory", 20 | "socketmon", 21 | "gui", 22 | "osx", 23 | "util", 24 | "linux" 25 | ] 26 | -------------------------------------------------------------------------------- /Peach/Strategies/default.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.strategy import * 5 | 6 | 7 | class StaticStrategy(Strategy): 8 | 9 | def __init__(self, stateMachine, routeDescriptor, params=None): 10 | Strategy.__init__(self, stateMachine, routeDescriptor) 11 | 12 | def _findRoute(self, start, destination): 13 | if destination is None: 14 | return [start] 15 | return [start, destination] 16 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/HTMLEncode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import xml.sax.saxutils 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class HtmlEncode(Transformer): 10 | """Perform standard HTML encoding of < > & and ".""" 11 | 12 | def realEncode(self, data): 13 | return xml.sax.saxutils.quoteattr(data).strip('"') 14 | 15 | def realDecode(self, data): 16 | return xml.sax.saxutils.unescape(data) 17 | -------------------------------------------------------------------------------- /Peach/Strategies/random.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.strategy import * 5 | 6 | 7 | class RandomStrategy(Strategy): 8 | 9 | def __init__(self, stateMachine, routeDescriptor, params=None): 10 | Strategy.__init__(self, stateMachine, routeDescriptor, params) 11 | 12 | def _findRoute(self, start, destination): 13 | """Todo""" 14 | print(int(self.params['maxsteps'])) 15 | return [start, destination] 16 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/UTF16.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class Utf16(Transformer): 8 | """Encode string as UTF-16. 9 | String is prefixed with BOM. Supports surrogate pair encoding of values 10 | larger then 0xFFFF. 11 | """ 12 | 13 | def realEncode(self, data): 14 | return data.encode("utf16") 15 | 16 | def realDecode(self, data): 17 | return str(data.decode("utf16")) 18 | -------------------------------------------------------------------------------- /Peach/Publishers/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import file 5 | import sql 6 | import stdout 7 | import tcp 8 | import udp 9 | import com 10 | import process 11 | import http 12 | import icmp 13 | import raw 14 | import remote 15 | import dll 16 | import smtp 17 | import wifi 18 | import sslayer 19 | 20 | 21 | __all__ = ["file", "sql", "stdout", 22 | "tcp", "udp", "com", "process", 23 | "http", "icmp", "raw", "remote", 24 | "dll", "smtp", "wifi", "sslayer"] 25 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/IPv4StringToOctet.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Ipv4StringToOctet(Transformer): 10 | """Convert a dot notation IPv4 address into a 4 byte octet representation. 11 | """ 12 | 13 | def realEncode(self, data): 14 | data = data.split('.') 15 | for i in range(len(data)): 16 | data[i] = int(data[i]) 17 | return pack('BBBB', data[0], data[1], data[2], data[3]) 18 | -------------------------------------------------------------------------------- /Peach/Generators/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import block 5 | import dictionary 6 | import incrementor 7 | import repeater 8 | import static 9 | import null 10 | import flipper 11 | import data 12 | import xmlstuff 13 | import uri 14 | 15 | __all__ = ["block", 16 | "dictionary", 17 | "incrementor", 18 | "repeater", 19 | "static", 20 | "null", 21 | #"unicode", 22 | "flipper", 23 | "data", 24 | "xmlstuff", 25 | "uri"] 26 | -------------------------------------------------------------------------------- /Peach/Transformers/Eval.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | from Peach import transformer 6 | 7 | 8 | class Eval(Transformer): 9 | """Eval a statement. 10 | Utility transformer for when all else fails. 11 | """ 12 | 13 | _eval = None 14 | 15 | def __init__(self, eval, anotherTransformer=None): 16 | transformer.Transformer.__init__(self, anotherTransformer) 17 | self._eval = eval 18 | 19 | def realEncode(self, data): 20 | return eval(self._eval % data) 21 | -------------------------------------------------------------------------------- /Peach/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import publisher 5 | import transformer 6 | import Publishers 7 | import Transformers 8 | import Engine 9 | import agent 10 | import mutator 11 | import Mutators 12 | import mutatestrategies 13 | import MutateStrategies 14 | import logger 15 | import Fixups 16 | import fixup 17 | __all__ = [ 18 | "publisher", 19 | "transformer", 20 | "Publishers", 21 | "Transformers", 22 | "Engine", 23 | "agent", 24 | "mutator", 25 | "Mutators", 26 | "fixup", 27 | "Fixups", 28 | "mutatestrategies", 29 | "MutateStrategies" 30 | ] 31 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/URLEncode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import urllib 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class UrlEncode(Transformer): 10 | """URL encode without pluses.""" 11 | 12 | def realEncode(self, data): 13 | return urllib.quote(data) 14 | 15 | def realDecode(self, data): 16 | return urllib.unquote(data) 17 | 18 | 19 | class UrlEncodePlus(Transformer): 20 | """URL encode with pluses.""" 21 | 22 | def realEncode(self, data): 23 | return urllib.quote_plus(data) 24 | 25 | def realDecode(self, data): 26 | return urllib.unquote_plus(data) 27 | -------------------------------------------------------------------------------- /Peach/Transformers/Cryptography/SHA1.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import hashlib 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Sha1(Transformer): 10 | """SHA-1 transform (hex and binary)""" 11 | 12 | _asHex = 0 13 | 14 | def __init__(self, asHex=0): 15 | """ 16 | @param asHex: 1 is hex, 0 is binary 17 | @type asHex: int 18 | """ 19 | Transformer.__init__(self) 20 | self._asHex = asHex 21 | 22 | def realEncode(self, data): 23 | if self._asHex == 0: 24 | return hashlib.sha1(data).digest() 25 | return hashlib.sha1(data).hexdigest() 26 | -------------------------------------------------------------------------------- /Peach/Transformers/Cryptography/MD5.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import hashlib 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Md5(Transformer): 10 | """MD5 transform (hex and binary)""" 11 | 12 | _asHex = 0 13 | 14 | def __init__(self, asHex=0): 15 | """ 16 | @param asHex: 1 is hex, 0 is binary 17 | @type asHex: int 18 | """ 19 | Transformer.__init__(self) 20 | self._asHex = asHex 21 | 22 | def realEncode(self, data): 23 | m = hashlib.md5() 24 | m.update(data) 25 | 26 | if self._asHex == 0: 27 | return m.digest() 28 | 29 | return m.hexdigest() -------------------------------------------------------------------------------- /Peach/Transformers/Encode/IPv4StringToNetworkOctet.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Ipv4StringToNetworkOctet(Transformer): 10 | """Convert a dot notation IPv4 address into a 4 byte octet representation. 11 | """ 12 | 13 | def realEncode(self, data): 14 | data = data.split('.') 15 | for i in range(len(data)): 16 | try: 17 | data[i] = int(data[i]) 18 | except Exception as e: 19 | data[i] = 0 20 | for i in range(len(data), 4): 21 | data.append(0) 22 | return pack('!BBBB', data[0], data[1], data[2], data[3]) 23 | -------------------------------------------------------------------------------- /Pits/Files/XML/xml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/ASCII7bit.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class Data7Bit(Transformer): 8 | def __init__(self): 9 | Transformer.__init__(self) 10 | 11 | def realEncode(self, data): 12 | result = [] 13 | count = 0 14 | last = 0 15 | for c in data: 16 | this = ord(c) << (8 - count) 17 | if count: 18 | result.append('%02X' % ((last >> 8) | (this & 0xFF))) 19 | count = (count + 1) % 8 20 | last = this 21 | result.append('%02x' % (last >> 8)) 22 | return ''.join(result) 23 | 24 | def realDecode(self, data): 25 | return data 26 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/SIDStringToBytes.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class SidStringToBytes(Transformer): 10 | """Transform a string representation of SID to a bytes. 11 | Format: S-1-5-21-2127521184-1604012920-1887927527-1712781 12 | """ 13 | 14 | def realEncode(self, data): 15 | sid = data.split('-') 16 | if len(sid) < 3 or sid[0] != 'S': 17 | raise Exception("Invalid SID string: {!s}".format(data)) 18 | ret = pack("BBBBBBBB", int(sid[1]), int(sid[2]), 0, 0, 0, 0, 0, 5) 19 | for i in range(int(sid[2])): 20 | ret += pack("I", int(sid[i + 3])) 21 | return ret -------------------------------------------------------------------------------- /Peach/Analyzers/pit.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.Engine.parser import * 5 | from Peach.Engine.common import * 6 | from Peach.analyzer import * 7 | 8 | 9 | class PitXmlAnalyzer(Analyzer): 10 | """ 11 | Analyzers produce data and state models. Examples of analyzers would be the parsing 12 | of PeachPit XML files, tokenizing a string, building a data model based on XML file, etc. 13 | """ 14 | 15 | supportParser = True 16 | 17 | def __init__(self): 18 | self.configs = None 19 | 20 | def asParser(self, uri): 21 | """ 22 | Called when Analyzer is used as default Pit parser. 23 | Should produce a Peach DOM. 24 | """ 25 | return ParseTemplate(self.configs).parse(uri) 26 | 27 | -------------------------------------------------------------------------------- /Peach/Transformers/Type/IntToHex.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from types import StringType 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class IntToHex(Transformer): 10 | """Transform an integer into hex.""" 11 | 12 | def __init__(self, withPrefix=0): 13 | """Create IntToHex object. 14 | withPrefix flag indicates if 0x prefix should be tagged onto string. 15 | Default is no. 16 | """ 17 | Transformer.__init__(self) 18 | self._withPrefix = withPrefix 19 | 20 | def realEncode(self, data): 21 | if type(data) == StringType: 22 | data = int(data) 23 | ret = hex(data) 24 | if self._withPrefix == 0: 25 | return ret[2:] 26 | return ret -------------------------------------------------------------------------------- /Peach/Analyzers/__init__.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import pit 5 | import shark 6 | import stringtoken 7 | import xml 8 | import binary 9 | import asn1 10 | 11 | from xml import XmlAnalyzer 12 | 13 | # Alias default analyzers 14 | XmlAnalyzer = xml.XmlAnalyzer 15 | Asn1Analyzer = asn1.Asn1Analyzer 16 | BinaryAnalyzer = binary.Binary 17 | PitXmlAnalyzer = pit.PitXmlAnalyzer 18 | WireSharkAnalyzer = shark.WireSharkAnalyzer 19 | StringTokenAnalyzer = stringtoken.StringTokenAnalyzer 20 | 21 | __all__ = ["xml", "shark", "stringtoken", "pit", "binary", "asn1", 22 | "XmlAnalyzer", 23 | "Asn1Analyzer", 24 | "BinaryAnalyzer", 25 | "PitXmlAnalyzer", 26 | "WireSharkAnalyzer", 27 | "StringTokenAnalyzer" 28 | ] 29 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/WideChar.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class WideChar(Transformer): 8 | """Transform a normal string into a wchar string. 9 | Does not convert unicode into wchar strings or anything super fancy. 10 | """ 11 | 12 | def realEncode(self, data): 13 | try: 14 | return data.encode("utf-16le") 15 | except Exception as e: 16 | pass 17 | ret = "" 18 | for c in data: 19 | ret += c + "\0" 20 | return ret 21 | 22 | def realDecode(self, data): 23 | try: 24 | return str(data.decode("utf-16le")) 25 | except Exception as e: 26 | pass 27 | ret = "" 28 | for i in range(0, len(data), 2): 29 | ret += data[i] 30 | return ret -------------------------------------------------------------------------------- /Peach/Transformers/Encode/HTMLEncodeAggressive.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class HtmlEncodeAggressive(Transformer): 8 | """Perform aggressive HTML encoding. Only alpha-nums will not be encoded. 9 | """ 10 | 11 | def realEncode(self, data): 12 | # Allow: a-z A-Z 0-9 SP , . 13 | # Allow (dec): 97-122 65-90 48-57 32 44 46 14 | if data is None or len(data) == 0: 15 | return "" 16 | out = "" 17 | for char in data: 18 | c = ord(char) 19 | if ((97 <= c <= 122) or 20 | (65 <= c <= 90) or 21 | (48 <= c <= 57) or 22 | c == 32 or c == 44 or c == 46): 23 | out += char 24 | else: 25 | out += "&#{};".format(c) 26 | return out 27 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/JSEncode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class JsEncode(Transformer): 8 | """Perform JavaScript encoding of a string.""" 9 | 10 | def realEncode(self, data): 11 | # Allow: a-z A-Z 0-9 SP , . 12 | # Allow (dec): 97-122 65-90 48-57 32 44 46 13 | if data is None or len(data) == 0: 14 | return "" 15 | out = '' 16 | for char in data: 17 | c = ord(char) 18 | if ((97 <= c <= 122) or 19 | (65 <= c <= 90) or 20 | (48 <= c <= 57) or 21 | c == 32 or c == 44 or c == 46): 22 | out += char 23 | elif c <= 127: 24 | out += "\\x%02X" % c 25 | else: 26 | out += "\\u%04X" % c 27 | return out -------------------------------------------------------------------------------- /Peach/Utilities/Gaia/atoms/gaia_lock_screen.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | 'use strict'; 6 | 7 | var GaiaLockScreen = { 8 | 9 | unlock: function() { 10 | 11 | let setlock = window.wrappedJSObject.SettingsListener.getSettingsLock(); 12 | let obj = {'screen.timeout': 0}; 13 | setlock.set(obj); 14 | 15 | window.wrappedJSObject.ScreenManager.turnScreenOn(); 16 | 17 | waitFor( 18 | function() { 19 | window.wrappedJSObject.LockScreen.unlock(); 20 | waitFor( 21 | function() { 22 | finish(window.wrappedJSObject.LockScreen.locked); 23 | }, 24 | function() { 25 | return !window.wrappedJSObject.LockScreen.locked; 26 | } 27 | ); 28 | }, 29 | function() { 30 | return !!window.wrappedJSObject.LockScreen; 31 | } 32 | ); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /Peach/Transformers/Compression/Bzip2.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import bz2 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Compress(Transformer): 10 | """Bzip2 compression transform. 11 | Allows for compression level selection (default is 9). 12 | """ 13 | 14 | def __init__(self, level=9): 15 | """ 16 | @type level: int 17 | @param level: The compress level parameter, if given, must be a number 18 | between 1 and 9; the default is 9. 19 | """ 20 | Transformer.__init__(self) 21 | self._level = level 22 | 23 | def realEncode(self, data): 24 | return bz2.compress(data, self._level) 25 | 26 | def realDecode(self, data): 27 | return bz2.decompress(data) 28 | 29 | 30 | class Bz2Decompress(Transformer): 31 | """Bzip2 decompression transform.""" 32 | 33 | def realEncode(self, data): 34 | return bz2.decompress(data) 35 | 36 | def realDecode(self, data): 37 | return bz2.compress(data, 6) 38 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/UTF16LE.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Utf16Le(Transformer): 10 | """Encode string as UTF-16LE. 11 | Supports surrogate pair encoding of values larger then 0xFFFF. 12 | """ 13 | 14 | def realEncode(self, data): 15 | ret = '' 16 | for c in data: 17 | if ord(c) <= 0xFF: 18 | ret += pack(">BB", ord(c), 0x00) 19 | elif ord(c) <= 0xFFFF: 20 | ret += pack(" 0xFFFF: 22 | # Perform surrogate pair encoding 23 | value = ord(c) # value 24 | value -= 0x10000 25 | valueHigh = value & 0xFFF # high bits 26 | valueLow = value & 0xFFF000 # low bits 27 | word1 = 0xD800 28 | word2 = 0xDC00 29 | word1 |= valueHigh 30 | word2 |= valueLow 31 | ret += pack("BB", 0x00, ord(c)) 19 | elif ord(c) <= 0xFFFF: 20 | ret += pack(">H", ord(c)) 21 | elif ord(c) > 0xFFFF: 22 | # Perform surrogate pair encoding 23 | value = ord(c) # value 24 | value -= 0x10000 25 | valueHigh = value & 0xFFF # high bits 26 | valueLow = value & 0xFFF000 # low bits 27 | word1 = 0xD800 28 | word2 = 0xDC00 29 | word1 |= valueHigh 30 | word2 |= valueLow 31 | ret += pack(" 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Peach/Transformers/Cryptography/HMAC.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import hmac 5 | import hashlib 6 | 7 | from Peach.transformer import Transformer 8 | 9 | 10 | class Hmac(Transformer): 11 | """HMAC as described in RFC 2104.""" 12 | 13 | _key = None 14 | _digestmod = None 15 | _asHex = None 16 | 17 | def __init__(self, key, digestmod=hashlib.md5, asHex=0): 18 | """ 19 | Key is a generator for HMAC key, digestmod is hash to use (md5 or sha) 20 | 21 | :param key: HMAC key 22 | :type key: Generator 23 | :param digestmod: which digest to use 24 | :type digestmod: MD5 or SHA hashlib object 25 | :param asHex: 1 is hex, 0 is binary 26 | :type asHex: int 27 | """ 28 | Transformer.__init__(self) 29 | self._key = key 30 | self._digestmod = digestmod 31 | self._asHex = asHex 32 | 33 | def realEncode(self, data): 34 | if self._asHex == 0: 35 | return hmac.new(self._key.getValue(), data, self._digestmod).digest() 36 | return hmac.new(self._key.getValue(), data, self._digestmod).hexdigest() 37 | -------------------------------------------------------------------------------- /Pits/Targets/stdout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/PDU.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class PhoneNumber(Transformer): 8 | def __init__(self): 9 | Transformer.__init__(self) 10 | 11 | def realEncode(self, data): 12 | s = "" 13 | 14 | # Add padding F to make length of phone number even 15 | s = s + "F" if len(data) % 2 else s 16 | 17 | # Split string in 2 byte segments 18 | segments = [s[i:i + 2] for i in range(0, len(s), 2)] 19 | 20 | # Reverse and join back together 21 | segments = "".join([i[1] + i[0] for i in segments]) 22 | 23 | return segments 24 | 25 | def realDecode(self, data): 26 | # NOTE: after Data.realDecode data is binary NOT semi decimal octets! 27 | 28 | # Split string in 2 bytes segments 29 | segments = [data[i] for i in range(0, len(data), 1)] 30 | 31 | segments = ["%02x" % ord(i) for i in segments] 32 | 33 | # Reverse and join back together 34 | segments = "".join([i[1] + i[0] for i in segments]) 35 | 36 | # Strip potential padding F 37 | s = segments[:-1] if segments[-1].upper() == "F" else segments 38 | 39 | return s 40 | -------------------------------------------------------------------------------- /Peach/fixup.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | class Fixup(object): 7 | """ 8 | Fixup of values in the data model. This is done by keeping an internal 9 | list of references that can be resolved during fixup(). Helper functions 10 | are provided in this base class for resolving elements in the data model. 11 | """ 12 | 13 | def __init__(self): 14 | self.context = None 15 | self.is_in_self = False 16 | 17 | def do_fixup(self): 18 | """Wrapper around fixup() to prevent endless recursion.""" 19 | if not self.is_in_self: 20 | try: 21 | self.is_in_self = True 22 | return self.fixup() 23 | finally: 24 | self.is_in_self = False 25 | 26 | def _getRef(self): 27 | """ 28 | After incoming data is cracked some elements move around. Peach will 29 | auto update parameters called "ref" but you will need to re-fetch the 30 | value using this method. 31 | """ 32 | for param in self.context.fixup: 33 | if param.name == "ref": 34 | return eval(param.defaultValue) 35 | 36 | def fixup(self): 37 | """Performs the required fixup.""" 38 | raise Exception("Fixup not implemented yet!") 39 | -------------------------------------------------------------------------------- /Peach/Utilities/network.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import socket 5 | import SocketServer 6 | import multiprocessing 7 | import SimpleHTTPServer 8 | import atexit 9 | import logging 10 | 11 | socketProcesses = [] 12 | assignedPorts = set() 13 | 14 | 15 | @atexit.register 16 | def cleanupSocketProcesses(): 17 | logging.debug("Killing socket processes.") 18 | for process in socketProcesses: 19 | process.join() 20 | 21 | 22 | def getUnboundPort(host="", minRange=30000, maxRange=35000, socket_type=1, assignOnlyOnce=False): 23 | for port in range(minRange, maxRange): 24 | if port in assignedPorts: 25 | continue 26 | 27 | sd = socket.socket(type=socket_type) 28 | try: 29 | sd.bind((host, port)) 30 | except socket.error as err: 31 | if err.errno != 48: 32 | print(err.strerror) 33 | sd = None 34 | else: 35 | sd.close() 36 | if assignOnlyOnce: 37 | assignedPorts.add(port) 38 | return port 39 | return -1 40 | 41 | 42 | def runHTTPDThread(): 43 | port = getUnboundPort(minRange=8000, maxRange=9000) 44 | httpd = SocketServer.TCPServer(("", port), SimpleHTTPServer.SimpleHTTPRequestHandler) 45 | p = multiprocessing.Process(target=httpd.serve_forever, args=()) 46 | p.start() 47 | socketProcesses.append(p) 48 | return port 49 | -------------------------------------------------------------------------------- /Peach/Fixups/numbers.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import random 5 | 6 | from Peach.fixup import Fixup 7 | 8 | 9 | class EvenNumber(Fixup): 10 | 11 | def __init__(self, ref): 12 | Fixup.__init__(self) 13 | self.ref = ref 14 | 15 | def fixup(self): 16 | ref = self.context.findDataElementByName(self.ref) 17 | stuff = ref.getValue() 18 | if stuff is None: 19 | raise Exception("Error: EvenNumberFixup was unable to locate " 20 | "[{}]".format(self.ref)) 21 | return stuff if stuff % 2 == 0 else stuff + 1 22 | 23 | 24 | class SequenceIncrementFixup(Fixup): 25 | """ 26 | Allows a field to emit a sequential value without adding additional test 27 | cases. This is useful for sequence numbers common in network protocols. 28 | """ 29 | 30 | num = 1 31 | 32 | def __init__(self): 33 | Fixup.__init__(self) 34 | 35 | def fixup(self): 36 | SequenceIncrementFixup.num += 1 37 | return SequenceIncrementFixup.num 38 | 39 | 40 | class SequenceRandomFixup(Fixup): 41 | """ 42 | Allows a field to emit a random value without adding additional test cases. 43 | This is useful for sequence numbers common in network protocols. 44 | """ 45 | 46 | def __init__(self): 47 | random.seed() 48 | Fixup.__init__(self) 49 | 50 | def fixup(self): 51 | return random.randint(0, (1 << self.context.size) - 1) 52 | -------------------------------------------------------------------------------- /Peach/Publishers/remote.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.Engine.engine import Engine 5 | from Peach.publisher import * 6 | 7 | 8 | class RemotePublisher(Publisher): 9 | """ 10 | Load a publisher on a remote Agent 11 | """ 12 | 13 | def __init__(self, agentName, name, cls, *args): 14 | Publisher.__init__(self) 15 | self._agentName = agentName 16 | self._name = name 17 | self._cls = cls 18 | self._args = args 19 | self._initialized = False 20 | 21 | def start(self): 22 | if not self._initialized: 23 | self._agent = Engine.context.agent[self._agentName] 24 | self._agent.PublisherInitialize(self._name, self._cls, self._args) 25 | self._initialized = True 26 | 27 | self._agent.PublisherStart(self._name) 28 | 29 | def stop(self): 30 | self._agent.PublisherStop(self._name) 31 | 32 | def send(self, data): 33 | self._agent.PublisherSend(self._name, data) 34 | 35 | def receive(self, size=None): 36 | self._agent.PublisherReceive(self._name, size) 37 | 38 | def call(self, method, args): 39 | self._agent.PublisherCall(self._name, method, args) 40 | 41 | def property(self, property, value=None): 42 | self._agent.PublisherProperty(self._name, value) 43 | 44 | def connect(self): 45 | self._agent.PublisherConnect(self._name) 46 | 47 | def accept(self): 48 | self._agent.PublisherAccept(self._name) 49 | 50 | def close(self): 51 | self._agent.PublisherClose(self._name) 52 | -------------------------------------------------------------------------------- /Peach/analyzer.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | class Analyzer(object): 7 | """ 8 | Analyzers produce data and state models. Examples of analyzers would be the parsing of Peach Pit XML files, 9 | tokenizing a string, building a data model based on XML file, etc. 10 | """ 11 | supportParser = False 12 | supportDataElement = False 13 | supportCommandLine = False 14 | supportTopLevel = False 15 | 16 | def asParser(self, uri): 17 | """ 18 | Called when Analyzer is used as default Pit parser. Produces a Peach DOM. 19 | """ 20 | raise Exception("This analyzer cannot be used as parser.") 21 | 22 | def asDataElement(self, parent, args, dataBuffer): 23 | """ 24 | Called when Analyzer is used in a data model. Returns a DataElement. 25 | """ 26 | raise Exception("This analyzer does not support being attached to a data element.") 27 | 28 | def asCommandLine(self, args): 29 | """ 30 | Called when Analyzer is used from command line. Produces a Peach Pit XML. 31 | """ 32 | raise Exception("This analyzer does not support being run from the command line.") 33 | 34 | def asTopLevel(self, peach, args): 35 | """ 36 | Called when Analyzer is used from top level. 37 | From the top level producing zero or more data models and state models is possible. 38 | 39 | :param args: arguments from elements. 40 | :type args: dict 41 | """ 42 | raise Exception("This analyzer does not support being used as a top level element.") 43 | -------------------------------------------------------------------------------- /Peach/Mutators/Values.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | # Default: Big Endian 6 | # Schema: (name, byteorder): [ values, ... ] 7 | 8 | CustomValues = \ 9 | { 10 | ("8bit", True): [ 11 | "\xff", 12 | "\xfe", 13 | "\x01", 14 | "\x80", 15 | "\x7f", 16 | ], 17 | ("16bit", True): [ 18 | "\xff\xff", 19 | "\xff\xfe", 20 | "\x7f\xff", 21 | "\x7f\xfe", 22 | "\x80\x00", 23 | "\x20\x00", # short << 2 24 | "\x40\x00", # ushort << 2 25 | "\x00\x01", 26 | "\x00\x00", 27 | ], 28 | ("32bit", True): [ 29 | "\xff\xff\xff\xff", 30 | "\xff\xff\xff\xfe", 31 | "\x7f\xff\xff\xff", 32 | "\x7f\xff\xff\xfe", 33 | "\x80\x00\x00\x00", 34 | "\x00\x00\x00\x01", # unsigned short z = x - y 35 | "\xff\xc4\x40\x0f", # width * height * 4 36 | "\x00\x00\x00\x00", # n/0, array length 37 | ], 38 | ("bytestr", False): [ 39 | "\x41\x41\x41\x41", 40 | "\x41\x41\x41\x41\x41\x41\x41" 41 | ], 42 | ("str", False): [ 43 | "1", 44 | "10", 45 | "100" 46 | ] 47 | # https://developer.apple.com/fonts/ttrefman/RM05/Chap5.html 48 | # 0-255 8bit value as syscall + instruction bytes 49 | # ... 50 | } 51 | 52 | # UTF-7 2B 2F 76 (38 | 39 | 2B | 2F) 53 | # UTF-8 EF BB BF 54 | # UTF-16 LE FE FF 55 | # UTF-16 BE FF FE 56 | # UTF-32 BE 00 00 FE FF 57 | # UTF-32 LE FF FE 00 00 -------------------------------------------------------------------------------- /Peach/Transformers/Type/Pack.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Pack(Transformer): 10 | """Simple pack transform. 11 | Only a single piece of data can be used. Useful to generate binary data 12 | from a generator. 13 | 14 | Format C Type Python 15 | x pad byte no value 16 | c char string of length 1 17 | b signed char integer 18 | B unsigned char integer 19 | h short integer 20 | H unsigned short integer 21 | i int integer 22 | I unsigned int long 23 | l long integer 24 | L unsigned long long 25 | q long long long 26 | Q unsigned long long long 27 | f float float 28 | d double float 29 | s char[] string 30 | p char[] string 31 | P void* integer 32 | """ 33 | 34 | def __init__(self, packFormat): 35 | """Create a Pack transformer. 36 | packFormat is a standard pack format string. Format string should only 37 | contain a single data place holder. 38 | """ 39 | Transformer.__init__(self) 40 | self._packFormat = packFormat 41 | 42 | def realEncode(self, data): 43 | return pack(self._packFormat, data) -------------------------------------------------------------------------------- /Peach/Transformers/Encode/NetBiosDecode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class NetBiosDecode(Transformer): 8 | """NetBiosName decode. 9 | @author: Blake Frantz 10 | """ 11 | 12 | def __init__(self, anotherTransformer=None): 13 | """Create Transformer object. 14 | 15 | :param anotherTransformer: a transformer to run next 16 | :type anotherTransformer: Transformer 17 | """ 18 | Transformer.__init__(self, anotherTransformer) 19 | 20 | def realEncode(self, data): 21 | if data % 2 != 0: 22 | raise Exception("Invalid NetBiosEncoding, length must be " 23 | "divisible by two.") 24 | decoded = "" 25 | data = data.upper() 26 | for cnt in range(0, len(data), 2): 27 | c1 = ord(data[cnt]) 28 | c2 = ord(data[cnt + 1]) 29 | part1 = (c1 - 0x41) * 16 30 | part2 = (c2 - 0x41) 31 | decoded += chr(part1 + part2) 32 | return decoded 33 | 34 | def realDecode(self, data): 35 | encoded = "" 36 | data = data.upper() 37 | if self._pad: 38 | while len(data) < 16: 39 | data += " " 40 | data = data[:16] 41 | for c in data: 42 | ascii = ord(c) 43 | encoded += chr((ascii / 16) + 0x41) 44 | encoded += chr((ascii - (ascii / 16 * 16) + 0x41)) 45 | # The 16th byte is the name scope id, set it to 'host' and 46 | # null term it. 47 | if self._pad: 48 | encoded = encoded[:30] 49 | encoded += '\x41' 50 | encoded += '\x41' 51 | return encoded 52 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/NetBiosEncode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class NetBiosEncode(Transformer): 8 | """NetBiosName encode. 9 | @author: Blake Frantz 10 | """ 11 | 12 | def __init__(self, anotherTransformer=None, pad=True): 13 | """Create Transformer object. 14 | 15 | :param anotherTransformer: A transformer to run next 16 | :type anotherTransformer: Transformer 17 | :param pad: will pad / trim encoded name to 32 bytes 18 | :type pad: bool 19 | """ 20 | Transformer.__init__(self, anotherTransformer) 21 | self._pad = pad 22 | 23 | def realEncode(self, data): 24 | encoded = "" 25 | data = data.upper() 26 | if self._pad: 27 | while len(data) < 16: 28 | data += " " 29 | data = data[:16] 30 | for c in data: 31 | ascii = ord(c) 32 | encoded += chr((ascii / 16) + 0x41) 33 | encoded += chr((ascii - (ascii / 16 * 16) + 0x41)) 34 | # The 16th byte is the name scope id, set it to 'host' and 35 | # null term it. 36 | if self._pad: 37 | encoded = encoded[:30] 38 | encoded += '\x41' 39 | encoded += '\x41' 40 | return encoded 41 | 42 | def realDecode(self, data): 43 | if data % 2 != 0: 44 | raise Exception("Invalid NetBiosEncoding, length must be " 45 | "divisible by two.") 46 | decoded = "" 47 | data = data.upper() 48 | for cnt in range(0, len(data), 2): 49 | c1 = ord(data[cnt]) 50 | c2 = ord(data[cnt + 1]) 51 | part1 = (c1 - 0x41) * 16 52 | part2 = (c2 - 0x41) 53 | decoded += chr(part1 + part2) 54 | return decoded -------------------------------------------------------------------------------- /Peach/Transformers/Cryptography/CVS.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class CvsScramble(Transformer): 8 | """CVS pserver password scramble.""" 9 | 10 | _shifts = [ 11 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 12 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 13 | 114, 120, 53, 79, 96, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, 14 | 111, 52, 75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, 15 | 41, 57, 83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, 16 | 125, 55, 54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, 17 | 36, 121, 117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, 18 | 58, 113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85, 223, 19 | 225, 216, 187, 166, 229, 189, 222, 188, 141, 249, 148, 200, 184, 136, 248, 190, 20 | 199, 170, 181, 204, 138, 232, 218, 183, 255, 234, 220, 247, 213, 203, 226, 193, 21 | 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145, 238, 161, 179, 160, 212, 22 | 207, 221, 254, 173, 202, 146, 224, 151, 140, 196, 205, 130, 135, 133, 143, 246, 23 | 192, 159, 244, 239, 185, 168, 215, 144, 139, 165, 180, 157, 147, 186, 214, 176, 24 | 227, 231, 219, 169, 175, 156, 206, 198, 129, 164, 150, 210, 154, 177, 134, 127, 25 | 182, 128, 158, 208, 162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171, 195, 26 | 243, 233, 253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152 27 | ] 28 | 29 | def realEncode(self, data): 30 | s = [] 31 | for i in range(len(data)): 32 | s.append(data[i]) 33 | s.append(None) 34 | for i in range(len(data)): 35 | s[i + 1] = "%c" % self._shifts[ord(data[i])] 36 | out = "" 37 | for i in range(len(s)): 38 | out += s[i] 39 | return out 40 | -------------------------------------------------------------------------------- /Peach/Generators/null.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import sys 5 | 6 | from Peach import generator 7 | 8 | 9 | class PrintStdout(generator.Generator): 10 | """ 11 | Logical value of empty string, but will display a value to stdout when 12 | called. Useful for displaying status messages. 13 | """ 14 | 15 | _msg = None 16 | _generator = None 17 | 18 | def __init__(self, msg, generator=None): 19 | """ 20 | @type msg: string 21 | @param msg: Value to output 22 | @type generator: Generator 23 | @param generator: Generator to wrap 24 | """ 25 | self._msg = msg 26 | self._generator = generator 27 | 28 | def getRawValue(self): 29 | print(self._msg) 30 | if self._generator: 31 | return self._generator.getRawValue() 32 | return "" 33 | 34 | def next(self): 35 | if self._generator: 36 | self._generator.next() 37 | else: 38 | raise generator.GeneratorCompleted("PrintStdout") 39 | 40 | 41 | class PrintStderr(generator.Generator): 42 | """ 43 | Logical value of empty string, but will display a value to stderr when 44 | called. Useful for displaying status messages. 45 | """ 46 | 47 | _msg = None 48 | _generator = None 49 | 50 | def __init__(self, msg, generator=None): 51 | """ 52 | @type msg: string 53 | @param msg: Value to output 54 | @type generator: Generator 55 | @param generator: Generator to wrap 56 | """ 57 | self._msg = msg 58 | self._generator = generator 59 | 60 | def getRawValue(self): 61 | sys.stderr.write(self._msg + "\n") 62 | if self._generator: 63 | return self._generator.getRawValue() 64 | return "" 65 | 66 | def next(self): 67 | if self._generator: 68 | self._generator.next() 69 | else: 70 | raise generator.GeneratorCompleted("PrintStderr") 71 | -------------------------------------------------------------------------------- /Peach/Utilities/common.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import sys 5 | import logging 6 | 7 | from Peach.Engine.common import highlight 8 | 9 | 10 | def isSupportedOS(platforms): 11 | return filter(lambda x: x == sys.platform, platforms) 12 | 13 | 14 | def isPosix(): 15 | return 'posix' in sys.builtin_module_names 16 | 17 | 18 | def isLinux(): 19 | return sys.platform == "linux2" 20 | 21 | 22 | def isMacOS(): 23 | return sys.platform == "darwin" 24 | 25 | 26 | def isWindows(): 27 | return sys.platform == "win32" 28 | 29 | 30 | def printHex(src): 31 | FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) 32 | N = 0 33 | result = '' 34 | length = 16 35 | while src: 36 | s, src = src[:length], src[length:] 37 | hexa = ' '.join(["%02X" % ord(x) for x in s]) 38 | s = s.translate(FILTER) 39 | result += "%04X %-*s %s\n" % (N, length * 3, hexa, s) 40 | N += length 41 | print(result) 42 | 43 | 44 | def getBooleanAttribute(args, name): 45 | val = args.get(name, '0').lower().replace("'''", "") 46 | result = val in ('true', 'yes', '1') 47 | if not result: 48 | assert val in ('false', 'no', '0') 49 | return result 50 | 51 | 52 | def getFloatAttribute(args, name, default=""): 53 | return float(args.get(name, default).replace("'''", "")) 54 | 55 | 56 | def getStringAttribute(args, name, default=""): 57 | return args.get(name, default).replace("'''", "") 58 | 59 | 60 | def setAttributesFromParams(node): 61 | if node is not None and node.get('params') is not None: 62 | for kv in node.get('params').split(','): 63 | try: 64 | k, v = [s.strip() for s in kv.split('=', 1)] 65 | except ValueError: 66 | logging.error(highlight.error("The macro %s has no value." % kv)) 67 | sys.exit(-1) 68 | k = k[0].lower() + k[1:] # CamelCase -> camelCase 69 | node.set(k, v) 70 | -------------------------------------------------------------------------------- /Peach/Agent/linux.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import sys 5 | import time 6 | import os 7 | 8 | from Peach.agent import Monitor 9 | 10 | 11 | class LinuxApport(Monitor): 12 | 13 | def __init__(self, args): 14 | if 'Executable' in args: 15 | self.programPath = str(args['ProgramPath']).replace("'''", "") 16 | self.processName = os.path.basename(self.programPath) 17 | else: 18 | self.processName = None 19 | 20 | if 'LogFolder' in args: 21 | self.logFolder = str(args['LogFolder']).replace("'''", "") 22 | else: 23 | self.logFolder = "/var/crash/" 24 | 25 | if 'Apport' in args: 26 | self.Apport = self.logFolder = str(args['Apport']).replace("'''", "") 27 | else: 28 | self.Apport = "/usr/share/apport/apport" 29 | 30 | self._name = "LinuxApport" 31 | 32 | self.data = None 33 | self.startingFiles = None 34 | 35 | def OnTestStarting(self): 36 | self.startingFiles = os.listdir(self.logFolder) 37 | 38 | def GetMonitorData(self): 39 | if not self.data: 40 | return None 41 | return {"LinuxApport.txt": self.data} 42 | 43 | def DetectedFault(self): 44 | try: 45 | time.sleep(0.25) 46 | time.sleep(0.25) 47 | self.data = None 48 | for f in os.listdir(self.logFolder): 49 | if f not in self.startingFiles and f.endswith(".crash") and \ 50 | (self.processName is None or f.find(self.processName) > -1): 51 | fd = open(os.path.join(self.logFolder, f), "rb") 52 | self.data = fd.read() 53 | fd.close() 54 | os.unlink(os.path.join(self.logFolder, f)) 55 | return True 56 | return False 57 | except: 58 | print(sys.exc_info()) 59 | return False 60 | 61 | def StopRun(self): 62 | return False 63 | -------------------------------------------------------------------------------- /Peach/Transformers/Compression/Gzip.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import zlib 5 | 6 | from Peach.transformer import Transformer 7 | 8 | 9 | class Compress(Transformer): 10 | """Gzip compression transformer. 11 | Allows compression level selection (default is 6). 12 | """ 13 | 14 | def __init__(self, level=6): 15 | """ 16 | :param level: an integer from 1 to 9 controlling the level of 17 | compression; 1 is fastest and produces the least 18 | compression, 9 is slowest and produces the most. 19 | The default value is 6. 20 | :type level: int 21 | """ 22 | Transformer.__init__(self) 23 | self._level = level 24 | self._wbits = 15 25 | 26 | def realEncode(self, data): 27 | return zlib.compress(data, self._level) 28 | 29 | def realDecode(self, data): 30 | return zlib.decompress(data, self._wbits) 31 | 32 | 33 | class Decompress(Transformer): 34 | """Gzip decompression transform.""" 35 | 36 | def __init__(self, wbits=15): 37 | """ 38 | :type wbits: int 39 | :param wbits: The absolute value of wbits is the base two logarithm 40 | of the size of the history buffer (the 'window size') 41 | used when compressing data. Its absolute value should be 42 | between 8 and 15 for the most recent versions of the 43 | zlib library, larger values resulting in better 44 | compression at the expense of greater memory usage. 45 | The default value is 15. When wbits is negative, the 46 | standard gzip header is suppressed; this is an 47 | undocumented feature of the zlib library, used for 48 | compatibility with unzip's compression file format. 49 | """ 50 | Transformer.__init__(self) 51 | self._wbits = wbits 52 | self._level = 6 53 | 54 | def realEncode(self, data): 55 | return zlib.decompress(data, self._wbits) 56 | 57 | def realDecode(self, data): 58 | return zlib.compress(data, self._level) 59 | -------------------------------------------------------------------------------- /Peach/Publishers/irc.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import sys 5 | import time 6 | import socket 7 | 8 | from Peach.Engine.common import PeachException 9 | from Peach.Publishers.tcp import TcpListener 10 | 11 | 12 | # 13 | # CAP REQ :multi-prefix 14 | # NICK cdiehl 15 | # USER cdiehl 0 * :Christoph Diehl 16 | # 17 | 18 | 19 | class FakeServer(TcpListener): 20 | def __init__(self, host, port): 21 | TcpListener.__init__(self, host, port) 22 | self._accepted = False 23 | 24 | def start(self): 25 | if self._listen is None: 26 | for i in range(3): 27 | try: 28 | self._listen = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 29 | self._listen.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 30 | self._listen.bind((self._host, self._port)) 31 | self._listen.listen(1) 32 | exception = None 33 | break 34 | except: 35 | self._listen = None 36 | exception = sys.exc_info() 37 | time.sleep(0.5) 38 | if self._listen is None: 39 | value = "" 40 | try: 41 | value = str(exception[1]) 42 | except: 43 | pass 44 | raise PeachException("TCP bind attempt failed: %s" % value) 45 | self.buff = "" 46 | self.pos = 0 47 | 48 | def accept(self): 49 | if not self._accepted: 50 | self.buff = "" 51 | self.pos = 0 52 | 53 | conn, addr = self._listen.accept() 54 | self._socket = conn 55 | self._clientAddr = addr 56 | self._accepted = True 57 | 58 | def close(self): 59 | pass 60 | 61 | def stop(self): 62 | pass 63 | 64 | 65 | 66 | # connectedUsers = [] 67 | # 68 | # request = client.recv(1024) 69 | # initialized = True 70 | # 71 | # request = request.split("\r\n") 72 | # for line in request: 73 | # if line.startswith("NICK"): 74 | # connectedUsers.append(line.split("NICK ")[1]) 75 | # 76 | # hostname = client.gethostname() 77 | # client.send(":%s %s") 78 | -------------------------------------------------------------------------------- /Peach/Transformers/Encode/HexString.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.transformer import Transformer 5 | 6 | 7 | class HexString(Transformer): 8 | """Transform a string of bytes into the specified hex format. 9 | 10 | Example: 11 | 12 | gen = Static("AAAABBBB").setTransformer(HexString()) 13 | print gen.getValue() 14 | 41 41 41 41 42 42 42 42 15 | 16 | gen = Static("AAAABBBB").setTransformer(HexString(None, 4, "0x")) 17 | print gen.getValue() 18 | 0x414141410x42424242 19 | 20 | gen = Static("AAAABBBB").setTransformer(HexString(None, 1, " \\x")) 21 | print gen.getValue() 22 | \x41 \x41 \x41 \x41 \x42 \x42 \x42 \x42 23 | """ 24 | 25 | def __init__(self, anotherTransformer=None, resolution=None, prefix=None): 26 | """Create Transformer object. 27 | 28 | :param anotherTransformer: a transformer to run next 29 | :type anotherTransformer: Transformer 30 | :param resolution: number of nibbles between separator 31 | (must be a positive even integer) 32 | :type resolution: int 33 | :param prefix: a value to prepend each chunk with (defaults to ' ') 34 | :type prefix: str 35 | """ 36 | 37 | Transformer.__init__(self, anotherTransformer) 38 | self._resolution = resolution 39 | self._prefix = prefix 40 | 41 | def realEncode(self, data): 42 | ret = '' 43 | if self._resolution is None: 44 | self._resolution = 1 45 | # Try to detect if user passed in an odd numbered value 46 | if self._resolution % 2 and self._resolution != 1: 47 | raise Exception("Resolution must be 1 or a multiple of two.") 48 | if len(data) % self._resolution != 0: 49 | raise Exception("Data length must be divisible by resolution.") 50 | if self._prefix is None: 51 | self._prefix = " " 52 | tmp = '' 53 | for c in data: 54 | h = hex(ord(c))[2:] 55 | if len(h) == 2: 56 | tmp += h 57 | else: 58 | tmp += "0%s" % h 59 | if len(tmp) / 2 == self._resolution: 60 | ret += self._prefix + tmp 61 | tmp = '' 62 | ret = ret.strip() 63 | return ret -------------------------------------------------------------------------------- /Peach/mutator.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.Engine.dom import * 5 | 6 | 7 | class MutatorCompleted(Exception): 8 | """ 9 | At end of available mutations. 10 | """ 11 | pass 12 | 13 | 14 | class MutatorError(Exception): 15 | pass 16 | 17 | 18 | class Mutator(object): 19 | """ 20 | A Mutator implements a method of mutating data/state for a Peach fuzzer. 21 | For example a mutator might change the state flow defined by a Peach 22 | fuzzer. Another mutator might mutate data based on known relationships. 23 | Another mutator might perform numerical type tests against fields. 24 | """ 25 | elementType = "mutator" 26 | dataTypes = [ 27 | 'template', 28 | 'string', 29 | 'number', 30 | 'flags', 31 | 'choice', 32 | 'sequence', 33 | 'blob', 34 | 'block' 35 | ] 36 | _random = random.Random() 37 | weight = 1 38 | 39 | def __init__(self): 40 | self.name = "Mutator" 41 | self._count = None 42 | self.changedName = "N/A" 43 | self.isFinite = False 44 | 45 | @staticmethod 46 | def supportedDataElement(node): 47 | """ 48 | Returns true if element is supported by this mutator. 49 | """ 50 | return isinstance(node, DataElement) and node.isMutable 51 | 52 | def next(self): 53 | """ 54 | Go to next mutation. When this is called the state machine is updated 55 | as needed. 56 | """ 57 | pass 58 | 59 | def getCount(self): 60 | """ 61 | If mutator is finite than the total test count can be calculated. 62 | This calculation cannot occur until after the state machine has been 63 | run the first time. Once the state machine has been run the count can 64 | be calculated. This typically occurs in a separate thread as it can 65 | take some time to calculate. This method will return None until a the 66 | correct value has been calculated. 67 | """ 68 | return self._count 69 | 70 | def sequentialMutation(self, node): 71 | """ 72 | Perform a sequential mutation. 73 | """ 74 | pass 75 | 76 | def randomMutation(self, node): 77 | """ 78 | Perform a random mutation. 79 | """ 80 | pass 81 | -------------------------------------------------------------------------------- /Peach/Publishers/sql.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from types import * 5 | 6 | import sys 7 | 8 | try: 9 | import dbi, odbc 10 | except ImportError: 11 | pass 12 | 13 | from Peach.publisher import Publisher 14 | 15 | #__all__ = ["Odbc"] 16 | 17 | 18 | class Odbc(Publisher): 19 | """ 20 | Publisher for ODBC connections. Generated data sent as a SQL query via 21 | execute. Calling receave will return a string of all row data concatenated 22 | together with \t as field separator. 23 | 24 | Currently this Publisher makes use of the odbc package which is some what 25 | broken in that you must create an actual ODBC DSN via the ODBC manager. 26 | Check out mxODBC which is not open source for another alterative. 27 | 28 | Note: Each call to start/stop will create and close the SQL connection and 29 | cursor. 30 | """ 31 | 32 | def __init__(self, dsn): 33 | """ 34 | @type dsn: string 35 | @param dsn: DSN must be in format of "dsn/user/password" where DSN is a DSN name. 36 | """ 37 | Publisher.__init__(self) 38 | self._dsn = dsn 39 | self._sql = None 40 | self._cursor = None 41 | self._sql = None 42 | 43 | def start(self): 44 | """ 45 | Create connection to server. 46 | """ 47 | self._sql = odbc.odbc(self._dsn) 48 | 49 | def stop(self): 50 | """ 51 | Close any open cursors, and close connection to server. 52 | """ 53 | self._cursor.close() 54 | self._cursor = None 55 | self._sql.close() 56 | self._sql = None 57 | 58 | def call(self, method, args): 59 | """ 60 | Create cursor and execute data. 61 | """ 62 | self._cursor = self._sql.cursor() 63 | 64 | try: 65 | self._cursor.execute(method, args) 66 | except: 67 | print("Warning: execute failed: %s" % sys.exc_info()) 68 | pass 69 | 70 | ret = '' 71 | try: 72 | row = self._cursor.fetchone() 73 | for i in range(len(row)): 74 | retType = type(row[i]) 75 | if retType is IntType: 76 | ret += "\t%d" % row[i] 77 | elif retType is FloatType: 78 | ret += "\t%f" % row[i] 79 | elif retType is LongType: 80 | ret += "\t%d" % row[i] 81 | elif retType is StringType: 82 | ret += "\t%s" % row[i] 83 | 84 | except: 85 | pass 86 | 87 | return ret 88 | -------------------------------------------------------------------------------- /Peach/Publishers/httpserver.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 5 | import urllib 6 | import urlparse 7 | import base64 8 | import threading 9 | 10 | from Peach.Publishers.websocket import WebSocketFilePublisher 11 | from Peach.Utilities.network import getUnboundPort 12 | 13 | 14 | class HeaderReflectHandler(BaseHTTPRequestHandler): 15 | """ 16 | A handler that reflects data in requests params to response headers 17 | """ 18 | def _write_header(self, header, value): 19 | self.send_header(header, value) 20 | 21 | def do_GET(self): 22 | """ 23 | Handle a GET request. Takes 2 request params; 'header' (the response 24 | header to write) and 'data' (base64 encoded data to be written to that 25 | header) 26 | """ 27 | qs = {} 28 | path = self.path 29 | self.send_response(200) 30 | if '?' in path: 31 | path, tmp = path.split('?', 1) 32 | qs = urlparse.parse_qs(tmp) 33 | if qs['data'] and qs['header']: 34 | self._write_header(qs['header'][0], base64.b64decode(qs['data'][0])) 35 | self.end_headers() 36 | self.wfile.write('Wow! Such fuzz!') 37 | return 38 | 39 | 40 | class HttpHeaderPublisher(WebSocketFilePublisher): 41 | """ 42 | A publisher for serving headers from an HTTP server. 43 | """ 44 | def initialize(self): 45 | WebSocketFilePublisher.initialize(self) 46 | # find a port we can bind for our webserver 47 | self.webserver_port = getUnboundPort() 48 | self.debug("attempting to bind %i for webserver" % self.webserver_port) 49 | server = HTTPServer(('0.0.0.0', self.webserver_port), HeaderReflectHandler) 50 | self._running = True 51 | 52 | # TODO: start the webserver 53 | def run_server(): 54 | while self._running: 55 | server.handle_request() 56 | 57 | t = threading.Thread(target=run_server) 58 | t.daemon = True 59 | t.start() 60 | 61 | def _prepareBrowserTemplate(self, data): 62 | if self._publish == "base64": 63 | data = self._contentTemplate.substitute(FILE=base64.b64encode(data), PORT=self.webserver_port) 64 | else: 65 | data = self._contentTemplate.substitute(FILE=urlparse.urljoin(self._publish, self._storagePath), PORT=self.webserver_port) 66 | data = urllib.quote(data) 67 | return data 68 | 69 | def finalize(self): 70 | WebSocketFilePublisher.finalize(self) 71 | self._running = False 72 | -------------------------------------------------------------------------------- /Peach/Publishers/icmp.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import socket 5 | import time 6 | 7 | from Peach.publisher import Publisher 8 | 9 | 10 | class Icmp(Publisher): 11 | """ 12 | A simple ICMP publisher. 13 | """ 14 | 15 | _host = None 16 | _socket = None 17 | 18 | def __init__(self, host, timeout=0.1): 19 | """ 20 | @type host: string 21 | @param host: Remote host 22 | @type timeout: number 23 | @param timeout: How long to wait for response 24 | """ 25 | Publisher.__init__(self) 26 | self._host = host 27 | self._timeout = float(timeout) 28 | 29 | def stop(self): 30 | """ 31 | Close connection if open. 32 | """ 33 | self.close() 34 | 35 | def connect(self): 36 | if self._socket is not None: 37 | # Close out old socket first 38 | self._socket.close() 39 | self._socket = socket.socket(socket.AF_INET, 40 | socket.SOCK_RAW, 41 | socket.getprotobyname('icmp')) 42 | self._socket.connect((self._host, 22)) 43 | 44 | def close(self): 45 | if self._socket is not None: 46 | self._socket.close() 47 | self._socket = None 48 | 49 | def send(self, data): 50 | """ 51 | Send data via sendall. 52 | 53 | @type data: string 54 | @param data: Data to send 55 | """ 56 | self._socket.sendall(data) 57 | 58 | def receive(self, size=None): 59 | """ 60 | Receive up to 10000 bytes of data. 61 | 62 | @rtype: string 63 | @return: received data. 64 | """ 65 | if size is not None: 66 | return self._socket.recv(size) 67 | else: 68 | self._socket.setblocking(0) 69 | timeout = self._timeout 70 | beginTime = time.time() 71 | ret = "" 72 | try: 73 | while True: 74 | if len(ret) > 0 or time.time() - beginTime > timeout: 75 | break 76 | try: 77 | ret += self._socket.recv(10000) 78 | except socket.error as e: 79 | if str(e).find("The socket operation could not " 80 | "complete without blocking") == -1: 81 | raise 82 | except socket.error as e: 83 | print("Tcp::Receive(): Caught socket.error [{}]".format(e)) 84 | self._socket.setblocking(1) 85 | return ret 86 | -------------------------------------------------------------------------------- /Peach/transformer.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | class Transformer(object): 7 | """ 8 | Transformers encode or decode some form of input e.g. Base64 string. 9 | Chained transformer run after this transformer. 10 | """ 11 | 12 | def __init__(self, anotherTransformer=None): 13 | """Create a Transformer object. 14 | 15 | :param anotherTransformer: a transformer to run next 16 | :type anotherTransformer: Transformer 17 | """ 18 | self._anotherTransformer = anotherTransformer 19 | 20 | def changesSize(self): 21 | return True 22 | 23 | def encode(self, data): 24 | """Transform data and call next transformer in chain if provided. 25 | 26 | :param data: data to transform 27 | :type data: str 28 | :returns: transformed data 29 | :rtype: str 30 | """ 31 | ret = self.realEncode(data) 32 | if self._anotherTransformer is not None: 33 | return self._anotherTransformer.encode(ret) 34 | return ret 35 | 36 | def decode(self, data): 37 | """Perform reverse transformation if possible. 38 | 39 | :param data: data to transform 40 | :type data: str 41 | :returns: transformed data 42 | :rtype: str 43 | """ 44 | if self._anotherTransformer is not None: 45 | data = self._anotherTransformer.decode(data) 46 | return self.realDecode(data) 47 | 48 | def getTransformer(self): 49 | """Gets the next transformer in the chain. 50 | 51 | :returns: next transformer in chain or None 52 | :rtype: Transformer 53 | """ 54 | return self._anotherTransformer 55 | 56 | def addTransformer(self, transformer): 57 | """Set the next transformer in the chain. 58 | 59 | :param transformer: transformer to set 60 | :type transformer: Transformer 61 | :returns: this transformer 62 | :rtype: Transformer 63 | """ 64 | self._anotherTransformer = transformer 65 | return self 66 | 67 | def realEncode(self, data): 68 | """Override this method to implement your transform. 69 | 70 | :param data: data to transform 71 | :type data: str 72 | :returns: transformed data 73 | :rtype: str 74 | """ 75 | raise Exception("realEncode(): Transformer does not work both ways.") 76 | 77 | def realDecode(self, data): 78 | """Override this method to implement your transform. 79 | 80 | :param data: data to transform 81 | :type data: str 82 | :returns: transformed data 83 | :rtype: str 84 | """ 85 | raise Exception("realDecode(): Transformer does not work both ways.") 86 | -------------------------------------------------------------------------------- /Peach/config.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import os 5 | import uuid 6 | import subprocess 7 | import re 8 | import atexit 9 | import shutil 10 | import logging 11 | import shlex 12 | 13 | from Peach.Utilities import network 14 | 15 | 16 | def getInstanceProvider(name): 17 | if name not in globals(): 18 | raise Exception("Invalid instance provider in configuration: " + name) 19 | return globals()[name] 20 | 21 | 22 | class FirefoxProfile(object): 23 | instances = {} 24 | 25 | def __init__(self, identifier, config): 26 | firefox_binary = shlex.split(config["DefaultBrowser"]) 27 | self.profile_name = identifier + "-" + str(uuid.uuid4()) 28 | output = subprocess.check_output(firefox_binary + 29 | ['-no-remote', 30 | '-CreateProfile', self.profile_name], 31 | stderr=subprocess.STDOUT) 32 | output = output.strip() 33 | if "Success: created profile" not in output: 34 | raise Exception("Unexpected output while creating Firefox profile: %s" % output) 35 | self.profile_prefs_path = re.findall("'.+?'", output)[1].strip("'") 36 | pref_path = config['DefaultFirefoxPrefs'] 37 | logging.debug(self.profile_prefs_path) 38 | shutil.copyfile(pref_path, self.profile_prefs_path) 39 | logging.debug("Successfully created temporary Firefox profile at %s" % self.profile_prefs_path) 40 | 41 | @staticmethod 42 | def getInstanceById(identifier, config): 43 | if identifier not in FirefoxProfile.instances: 44 | FirefoxProfile.instances[identifier] = FirefoxProfile(identifier, config) 45 | return FirefoxProfile.instances[identifier] 46 | 47 | @staticmethod 48 | @atexit.register 49 | def cleanInstances(): 50 | for instance in FirefoxProfile.instances.values(): 51 | instance.destroy() 52 | 53 | def __repr__(self): 54 | return self.profile_name 55 | 56 | def destroy(self): 57 | if self.profile_prefs_path.endswith('/prefs.js'): 58 | profile_path = os.path.dirname(self.profile_prefs_path) 59 | logging.debug("Deleting temporary Firefox profile at %s" % profile_path) 60 | shutil.rmtree(profile_path) 61 | 62 | 63 | class TCPPort(object): 64 | instances = {} 65 | 66 | def __init__(self, identifier, config): 67 | self.port_number = network.getUnboundPort(assignOnlyOnce=True) 68 | if self.port_number < 0: 69 | raise Exception("Unable to allocate free TCP port") 70 | 71 | @staticmethod 72 | def getInstanceById(identifier, config): 73 | if identifier not in TCPPort.instances: 74 | TCPPort.instances[identifier] = TCPPort(identifier, config) 75 | return TCPPort.instances[identifier] 76 | 77 | def __repr__(self): 78 | return str(self.port_number) 79 | -------------------------------------------------------------------------------- /Peach/Mutators/path.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.mutator import * 5 | from Peach.mutatestrategies import * 6 | 7 | 8 | class NullMutator(Mutator): 9 | """ 10 | Does not make any changes to data tree. This is usually the first mutator 11 | applied to a fuzzing run so the generated data can be verified. 12 | """ 13 | 14 | def __init__(self, peach): 15 | Mutator.__init__(self) 16 | self.name = "NullMutator" 17 | 18 | def isFinite(self): 19 | """ 20 | Some mutators could continue forever, this should indicate. 21 | """ 22 | return True 23 | 24 | def next(self): 25 | """ 26 | Go to next mutation. When this is called the state machine is updated 27 | as needed. 28 | """ 29 | raise MutatorCompleted() 30 | 31 | def getState(self): 32 | """ 33 | Return a binary string that contains any information about current 34 | state of Mutator. This state information should be enough to let the 35 | same mutator "restart" and continue when setState() is called. 36 | """ 37 | return "" 38 | 39 | def setState(self, state): 40 | """ 41 | Set the state of this object. Should put us back in the same place as 42 | when we said "getState()". 43 | """ 44 | pass 45 | 46 | def getCount(self): 47 | return 1 48 | 49 | def getActionValue(self, action): 50 | if action.template.modelHasOffsetRelation: 51 | stringBuffer = StreamBuffer() 52 | action.template.getValue(stringBuffer) 53 | stringBuffer.setValue("") 54 | stringBuffer.seekFromStart(0) 55 | action.template.getValue(stringBuffer) 56 | return stringBuffer.getValue() 57 | return action.template.getValue() 58 | 59 | def getActionParamValue(self, action): 60 | if action.template.modelHasOffsetRelation: 61 | stringBuffer = StreamBuffer() 62 | action.template.getValue(stringBuffer) 63 | stringBuffer.setValue("") 64 | stringBuffer.seekFromStart(0) 65 | action.template.getValue(stringBuffer) 66 | return stringBuffer.getValue() 67 | return action.template.getValue() 68 | 69 | def getActionChangeStateValue(self, action, value): 70 | return value 71 | 72 | 73 | class PathValidationMutator(NullMutator, MutationStrategy): 74 | """ 75 | This mutator is just used to trace path of each test for path validation 76 | purposes so this is not an actual Mutator that is used on fuzzing. 77 | """ 78 | 79 | def __init__(self): 80 | Mutator.__init__(self) 81 | self.states = [] 82 | self.name = "PathValidationMutator" 83 | 84 | def onStateStarting(self, stateMachine, state): 85 | self.states.append(state.name) 86 | 87 | def onStateMachineStarting(self, engine): 88 | pass 89 | 90 | def onStateMachineComplete(self, engine): 91 | engine.pathFinder.reset() 92 | -------------------------------------------------------------------------------- /Peach/Publishers/dll.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import ctypes 5 | from Peach.publisher import Publisher 6 | 7 | 8 | class Dll(Publisher): 9 | """ 10 | Shared library publisher using ctypes 11 | """ 12 | 13 | def __init__(self, library): 14 | Publisher.__init__(self) 15 | self.library = library 16 | self.dll = None 17 | self.withNode = True 18 | 19 | def start(self): 20 | try: 21 | self.dll = ctypes.cdll.LoadLibrary(self.library) 22 | 23 | except: 24 | print("Caught exception loading library [%s]" % self.library) 25 | raise 26 | 27 | def stop(self): 28 | self.dll = None 29 | 30 | def callWithNode(self, method, args, argNodes): 31 | """ 32 | Call method on COM object. 33 | 34 | @type method: string 35 | @param method: Name of method to invoke 36 | @type args: array of objects 37 | @param args: Arguments to pass 38 | """ 39 | 40 | self._lastReturn = None 41 | 42 | #ct = argNodes[0].asCType() 43 | #print ct 44 | #print ct.contents 45 | #print ct._fields_ 46 | #print ct.Named_37 47 | #print ct.Named_38 48 | 49 | try: 50 | ret = None 51 | callStr = "self.dll.%s(" % str(method) 52 | 53 | if len(args) > 0: 54 | for i in range(0, len(args)): 55 | callStr += "argNodes[%d].asCType()," % i 56 | 57 | callStr = callStr[:len(callStr) - 1] + ")" 58 | 59 | else: 60 | callStr += ")" 61 | 62 | ret = eval(callStr) 63 | return ret 64 | 65 | except: 66 | print("dll.Dll(): Caught unknown exception making call to %s" % method) 67 | raise 68 | 69 | #def property(self, property, value = None): 70 | # ''' 71 | # Get or set property 72 | # 73 | # @type property: string 74 | # @param property: Name of method to invoke 75 | # @type value: object 76 | # @param value: Value to set. If None, return property instead 77 | # ''' 78 | # 79 | # try: 80 | # if value == None: 81 | # ret = None 82 | # callStr = "ret = self._object.%s" % str(property) 83 | # 84 | # #print "Call string:", callStr 85 | # exec callStr 86 | # return ret 87 | # 88 | # ret = None 89 | # callStr = "self._object.%s = value" % str(property) 90 | # 91 | # #print "Call string:", callStr 92 | # exec callStr 93 | # return None 94 | # 95 | # except pywintypes.com_error, e: 96 | # print "Caught pywintypes.com_error on property [%s]" % e 97 | # #raise 98 | # 99 | # except NameError, e: 100 | # print "Caught NameError on property [%s]" % e 101 | # #raise 102 | # 103 | # except: 104 | # print "Com::property(): Caught unknown exception" 105 | # raise 106 | # 107 | # return None 108 | -------------------------------------------------------------------------------- /Peach/Engine/defaults.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Peach/Publishers/stdout.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import logging 5 | 6 | from Peach.publisher import Publisher 7 | from Peach.Utilities.common import printHex 8 | 9 | 10 | class Stdout(Publisher): 11 | """ 12 | Basic stdout publisher. All data is written to stdout. 13 | No input is available from this publisher. 14 | """ 15 | 16 | def accept(self): 17 | pass 18 | 19 | def connect(self): 20 | pass 21 | 22 | def close(self): 23 | pass 24 | 25 | def send(self, data): 26 | print(data) 27 | 28 | def receive(self, size=None): 29 | return "" 30 | 31 | def call(self, method, args): 32 | str = "" 33 | for a in args: 34 | str += "%s, " % repr(a) 35 | print("%s(%s)" % (method, str[:-2])) 36 | return "" 37 | 38 | 39 | class StdoutHex(Publisher): 40 | """ 41 | Basic stdout publisher that emits a hex dump. All data is written to stdout. 42 | No input is available from this publisher. 43 | """ 44 | 45 | def accept(self): 46 | pass 47 | 48 | def connect(self): 49 | pass 50 | 51 | def close(self): 52 | pass 53 | 54 | def send(self, src): 55 | # http://code.activestate.com/recipes/142812/ 56 | FILTER = "".join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) 57 | N = 0 58 | result = "" 59 | length = 16 60 | while src: 61 | s, src = src[:length], src[length:] 62 | hexa = " ".join(["%02X" % ord(x) for x in s]) 63 | s = s.translate(FILTER) 64 | result += "%04X %-*s %s\n" % (N, length * 3, hexa, s) 65 | N += length 66 | logging.debug("Hexdump of mutated data:\n%s" % result) 67 | 68 | def receive(self, size=None): 69 | return "" 70 | 71 | def call(self, method, args): 72 | str = "" 73 | for a in args: 74 | str += "%s, " % repr(a) 75 | print("%s(%s)" % (method, str[:-2])) 76 | return "" 77 | 78 | 79 | class Null(Publisher): 80 | """ 81 | Basic stdout publisher. All data is written to stdout. 82 | No input is available from this publisher. 83 | """ 84 | 85 | def accept(self): 86 | pass 87 | 88 | def connect(self): 89 | pass 90 | 91 | def close(self): 92 | pass 93 | 94 | def send(self, data): 95 | pass 96 | 97 | def receive(self): 98 | return "" 99 | 100 | def call(self, method, args): 101 | return "" 102 | 103 | 104 | class StdoutCtypes(Publisher): 105 | withNode = True 106 | 107 | def connect(self): 108 | pass 109 | 110 | def sendWithNode(self, data, dataNode): 111 | """ 112 | Publish some data 113 | 114 | @type data: string 115 | @param data: Data to publish 116 | @type dataNode: DataElement 117 | @param dataNode: Root of data model that produced data 118 | """ 119 | value = dataNode.asCType() 120 | print(value) 121 | print(dir(value)) 122 | 123 | def close(self): 124 | """ 125 | Close current stream/connection. 126 | """ 127 | pass 128 | -------------------------------------------------------------------------------- /Pits/Targets/console.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Peach/Agent/util.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import os 5 | import shutil 6 | 7 | from Peach.agent import Monitor 8 | 9 | 10 | class CleanupFolder(Monitor): 11 | """ 12 | This monitor will remove any files created in a folder during a fuzzing 13 | iteration. Create for removing stale temp files, etc. 14 | """ 15 | 16 | def __init__(self, args): 17 | """ 18 | Constructor. Arguments are supplied via the Peach XML file. 19 | 20 | @type args: Dictionary 21 | @param args: Dictionary of parameters 22 | """ 23 | self._name = None 24 | self._folder = args['Folder'].replace("'''", "") 25 | self._folderListing = os.listdir(self._folder) 26 | 27 | def OnTestStarting(self): 28 | """ 29 | Called right after a test case or variation. 30 | """ 31 | listing = os.listdir(self._folder) 32 | for item in listing: 33 | if item not in self._folderListing: 34 | realName = os.path.join(self._folder, item) 35 | print("CleanupFolder: Removing '{}'".format(realName)) 36 | try: 37 | os.unlink(realName) 38 | except: 39 | pass 40 | try: 41 | shutil.rmtree(realName) 42 | except: 43 | pass 44 | 45 | 46 | try: 47 | import win32api, win32con 48 | except: 49 | pass 50 | 51 | 52 | class CleanupRegistry(Monitor): 53 | """ 54 | This monitor will remove any sub-keys for a specified registry key before 55 | each run. This is useful for removing document recovery keys for fuzzing 56 | Office. 57 | """ 58 | 59 | def __init__(self, args): 60 | """ 61 | Constructor. Arguments are supplied via the Peach XML file. 62 | 63 | @type args: Dictionary 64 | @param args: Dictionary of parameters 65 | """ 66 | self._name = None 67 | self._key = args['Key'].replace("'''", "") 68 | 69 | if self._key.startswith("HKCU\\"): 70 | self._root = win32con.HKEY_CURRENT_USER 71 | elif self._key.startswith("HKCC\\"): 72 | self._root = win32con.HKEY_CURRENT_CONFIG 73 | elif self._key.startswith("HKLM\\"): 74 | self._root = win32con.HKEY_LOCAL_MACHINE 75 | elif self._key.startswith("HKPD\\"): 76 | self._root = win32con.HKEY_PERFORMANCE_DATA 77 | elif self._key.startswith("HKU\\"): 78 | self._root = win32con.HKEY_USERS 79 | else: 80 | print("CleanupRegistry: Error, key must be prefixed with: " 81 | "HKCU, HKCC, HKLM, HKPD, or HKU.") 82 | raise Exception("CleanupRegistry: Error, key must be prefixed " 83 | "with: HKCU, HKCC, HKLM, HKPD, or HKU.") 84 | self._key = self._key[self._key.find("\\") + 1:] 85 | 86 | def OnTestStarting(self): 87 | self.deleteKey(self._root, self._key) 88 | 89 | def deleteKey(self, hKey, subKey): 90 | """ 91 | Recursively remove registry keys. 92 | """ 93 | try: 94 | hKey = win32api.RegOpenKeyEx(hKey, subKey, 0, 95 | win32con.KEY_ALL_ACCESS) 96 | try: 97 | while True: 98 | s = win32api.RegEnumKey(hKey, 0) 99 | self.deleteKey(hKey, s) 100 | print("CleanupRegistry: Removing sub-key '{}'".format(s)) 101 | win32api.RegDeleteKey(hKey, s) 102 | except win32api.error: 103 | pass 104 | finally: 105 | win32api.RegCloseKey(hKey) 106 | except: 107 | print("Warning: Unable to open registry key!") 108 | pass 109 | -------------------------------------------------------------------------------- /Peach/Publishers/sslayer.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import sys 5 | import ssl 6 | import socket 7 | import tlslite.api 8 | 9 | from Peach.Publishers.tcp import TcpListener 10 | from Peach.publisher import PublisherSoftException 11 | from Peach.Engine.common import PeachException 12 | 13 | 14 | class SSL(TcpListener): 15 | def __init__(self, host, port, cert, pkey, timeout=0.25): 16 | TcpListener.__init__(self, host, port, timeout) 17 | 18 | self.cert = cert 19 | self.pkey = pkey 20 | 21 | def accept(self): 22 | print("[*] Waiting for incoming connection") 23 | client, addr = self._listen.accept() 24 | print("[*] Client:", addr[0], addr[1]) 25 | 26 | print("[*] Wrapping socket to TLS/SSL") 27 | try: 28 | self._socket = ssl.wrap_socket(client, 29 | server_side=True, 30 | certfile=self.cert, 31 | keyfile=self.pkey, 32 | do_handshake_on_connect=False) 33 | except ssl.SSLError as e: 34 | raise PeachException(str(e)) 35 | 36 | print("[*] Performing TLS/SSL handshake") 37 | try: 38 | self._socket.do_handshake() 39 | except ssl.SSLError as e: 40 | raise PeachException(str(e)) 41 | 42 | def close(self): 43 | try: 44 | if self._socket is not None: 45 | self._socket.shutdown(socket.SHUT_RDWR) 46 | self._socket.close() 47 | except: 48 | pass 49 | finally: 50 | self._socket = None 51 | 52 | 53 | class TLSLiteServer(TcpListener): 54 | def __init__(self, host, port, version, cert, pkey, timeout=0.25): 55 | TcpListener.__init__(self, host, port, timeout) 56 | 57 | self.cert = cert 58 | self.pkey = pkey 59 | self.version = version 60 | 61 | try: 62 | with open(self.cert) as fd: 63 | cert_content = fd.read() 64 | except IOError: 65 | raise PeachException("Unable to open %s" % self.cert) 66 | 67 | x509 = tlslite.api.X509() 68 | x509.parse(cert_content) 69 | self.certChain = tlslite.api.X509CertChain([x509]) 70 | 71 | try: 72 | with open(self.pkey) as fd: 73 | pkey_content = fd.read() 74 | except IOError: 75 | raise PeachException("Unable to open %s" % self.pkey) 76 | 77 | self.privateKey = tlslite.api.parsePEMKey(pkey_content, private=True) 78 | 79 | def accept(self): 80 | print("[*] Waiting for incoming connection") 81 | sys.stdout.flush() 82 | client, addr = self._listen.accept() 83 | 84 | print("[*] Connected by %s:%s" % (addr[0], str(addr[1]))) 85 | print("[*] Wrapping socket to TLS/SSL") 86 | try: 87 | self._socket = tlslite.api.TLSConnection(client) 88 | except: 89 | client.close() 90 | value = sys.exc_info()[1] 91 | msg = "[!] Wrapping socket failed, reason: %s" % value 92 | raise PublisherSoftException(msg) 93 | 94 | print("[*] Performing TLS/SSL handshake)") 95 | try: 96 | self._socket.handshakeServer(certChain=self.certChain, 97 | privateKey=self.privateKey, 98 | #reqCert=True, 99 | nextProtos=[self.version]) 100 | except: 101 | self.close() 102 | value = sys.exc_info()[1] 103 | msg = "[!] Performing TLS/SSL handshake failed, reason: %s" % value 104 | raise PublisherSoftException(msg) 105 | print("done!") 106 | 107 | 108 | class SPDYPublisher(TLSLiteServer): 109 | def __init__(self, host, port, cert, pkey, timeout=0.25): 110 | TLSLiteServer.__init__(self, host, port, cert, pkey, timeout) 111 | -------------------------------------------------------------------------------- /Peach/Transformers/Cryptography/ASN1.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | try: 5 | from struct import * 6 | from Peach.transformer import Transformer 7 | from pyasn1.type import univ 8 | from pyasn1.codec import der, ber, cer 9 | 10 | class DerEncodeOctetString(Transformer): 11 | """DER encode an octect string ASN.1 style.""" 12 | 13 | def realEncode(self, data): 14 | return der.encoder.encode(univ.OctetString(data)) 15 | 16 | class DerEncodeBitString(Transformer): 17 | """DER encode a bit string ASN.1 style.""" 18 | 19 | def realEncode(self, data): 20 | return der.encoder.encode(univ.BitString(data)) 21 | 22 | class DerEncodeInteger(Transformer): 23 | """DER encode an integer ASN.1 style.""" 24 | 25 | def realEncode(self, data): 26 | return der.encoder.encode(univ.Integer(int(data))) 27 | 28 | class DerEncodeBoolean(Transformer): 29 | """DER encode a boolean ASN.1 style. Expects 0 or 1.""" 30 | 31 | def realEncode(self, data): 32 | data = int(data) 33 | if data != 0 and data != 1: 34 | raise Exception("DerEncodeBoolean transformer expects 0 or 1") 35 | return der.encoder.encode(univ.Boolean(data)) 36 | 37 | class DerEncodeObjectIdentifier(Transformer): 38 | """DER encode an object identifierASN.1 style.""" 39 | 40 | def realEncode(self, data): 41 | return der.encoder.encode(univ.ObjectIdentifier(data)) 42 | 43 | class BerEncodeOctetString(Transformer): 44 | """BER encode a string ASN.1 style.""" 45 | 46 | def realEncode(self, data): 47 | return ber.encoder.encode(univ.OctetString(data)) 48 | 49 | class BerEncodeBitString(Transformer): 50 | """BER encode a bit string ASN.1 style.""" 51 | 52 | def realEncode(self, data): 53 | return ber.encoder.encode(univ.BitString(data)) 54 | 55 | class BerEncodeInteger(Transformer): 56 | """BER encode an integer ASN.1 style.""" 57 | 58 | def realEncode(self, data): 59 | return ber.encoder.encode(univ.Integer(int(data))) 60 | 61 | class BerEncodeBoolean(Transformer): 62 | """BER encode a boolean ASN.1 style. Expects 0 or 1.""" 63 | 64 | def realEncode(self, data): 65 | data = int(data) 66 | if data != 0 and data != 1: 67 | raise Exception("BerEncodeBoolean transformer expects 0 or 1") 68 | return ber.encoder.encode(univ.Boolean(data)) 69 | 70 | class BerEncodeObjectIdentifier(Transformer): 71 | """BER encode an object identifierASN.1 style.""" 72 | 73 | def realEncode(self, data): 74 | return ber.encoder.encode(univ.ObjectIdentifier(data)) 75 | 76 | class CerEncodeOctetString(Transformer): 77 | """CER encode a string ASN.1 style.""" 78 | 79 | def realEncode(self, data): 80 | return cer.encoder.encode(univ.OctetString(data)) 81 | 82 | class CerEncodeBitString(Transformer): 83 | """CER encode a bit string ASN.1 style.""" 84 | 85 | def realEncode(self, data): 86 | return cer.encoder.encode(univ.BitString(data)) 87 | 88 | class CerEncodeInteger(Transformer): 89 | """CER encode an integer ASN.1 style.""" 90 | 91 | def realEncode(self, data): 92 | return cer.encoder.encode(univ.Integer(int(data))) 93 | 94 | class CerEncodeBoolean(Transformer): 95 | """CER encode a boolean ASN.1 style. Expects 0 or 1.""" 96 | 97 | def realEncode(self, data): 98 | data = int(data) 99 | if data != 0 and data != 1: 100 | raise Exception("CerEncodeBoolean transformer expects 0 or 1") 101 | return cer.encoder.encode(univ.Boolean(data)) 102 | 103 | class CerEncodeObjectIdentifier(Transformer): 104 | """CER encode an object identifierASN.1 style.""" 105 | 106 | def realEncode(self, data): 107 | return cer.encoder.encode(univ.ObjectIdentifier(data)) 108 | except Exception as e: 109 | pass 110 | -------------------------------------------------------------------------------- /Peach/Generators/flipper.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import random 5 | import struct 6 | from Peach.generator import * 7 | 8 | 9 | class SequentialFlipper(SimpleGenerator): 10 | """ 11 | Sequentially flips bits in a data blob. This is for "random" fuzzing. 12 | Useful brute forcing, codecs, etc. 13 | """ 14 | 15 | _data = None 16 | _position = 0 17 | 18 | def __init__(self, group, data): 19 | """ 20 | @type data: string 21 | @param data: Binary static blob to flip 22 | """ 23 | SimpleGenerator.__init__(self, group) 24 | self._data = str(data) # Could be a unicode string. 25 | self._len = len(data) 26 | self._position = 0 27 | self._bit = 0 28 | 29 | def next(self): 30 | if self._bit == 7: 31 | self._position += 1 32 | self._bit = 0 33 | if self._position >= self._len: 34 | self._position -= 1 35 | self._bit = 7 36 | raise GeneratorCompleted("all done here") 37 | else: 38 | self._bit += 1 39 | 40 | def reset(self): 41 | self._bit = 0 42 | self._position = 0 43 | 44 | def getRawValue(self): 45 | if self._position >= self._len: 46 | return "" 47 | data = self._data 48 | byte = struct.unpack('B', data[self._position])[0] 49 | mask = 1 << self._bit 50 | if (byte & mask) >> self._bit == 1: 51 | mask = 0 52 | for i in range(8 - self._bit): 53 | mask |= 1 << i 54 | mask <<= 1 55 | if self._bit > 1: 56 | mask <<= self._bit 57 | for i in range(self._bit): 58 | mask |= 1 << i 59 | byte &= mask 60 | else: 61 | byte |= mask 62 | packedup = struct.pack("B", byte) 63 | data = data[:self._position] + packedup + data[self._position + 1:] 64 | return data 65 | 66 | 67 | class PartialFlipper(SimpleGenerator): 68 | """ 69 | Performs flips of 20% of bits in data. 70 | """ 71 | 72 | _data = None 73 | _position = 0 74 | 75 | def __init__(self, group, data, maxRounds=None): 76 | """ 77 | @type data: string 78 | @param data: Binary static blob to flip 79 | @type maxRounds: int 80 | @param maxRounds: optional, override 0.2% with a fixed number 81 | """ 82 | SimpleGenerator.__init__(self, group) 83 | 84 | self._data = str(data) # Could be a unicode string. 85 | self._len = len(data) 86 | self._position = 0 87 | self._bit = 0 88 | self._maxRounds = int((len(data) * 8) * 0.20) 89 | self._round = 0 90 | 91 | if maxRounds is not None: 92 | self._maxRounds = maxRounds 93 | 94 | def next(self): 95 | self._round += 1 96 | # Exit if we are completed with rounds, or have no data 97 | # to flip 98 | if self._round > self._maxRounds or (len(self._data) - 1) < 1: 99 | raise GeneratorCompleted("all done here") 100 | self._position = random.randint(0, len(self._data) - 1) 101 | self._bit = random.randint(0, 7) 102 | 103 | def reset(self): 104 | self._bit = 0 105 | self._position = 0 106 | self._round = 0 107 | 108 | def getRawValue(self): 109 | if self._position >= self._len: 110 | return "" 111 | data = self._data 112 | byte = struct.unpack('B', data[self._position])[0] 113 | mask = 1 << self._bit 114 | if (byte & mask) >> self._bit == 1: 115 | mask = 0 116 | for i in range(8 - self._bit): 117 | mask |= 1 << i 118 | mask <<= 1 119 | if self._bit > 1: 120 | mask <<= self._bit 121 | for i in range(self._bit): 122 | mask |= 1 << i 123 | byte &= mask 124 | else: 125 | byte |= mask 126 | packedup = struct.pack("B", byte) 127 | data = data[:self._position] + packedup + data[self._position + 1:] 128 | return data 129 | -------------------------------------------------------------------------------- /Peach/Transformers/Type/NumberToString.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | from struct import unpack 6 | from types import IntType 7 | from types import FloatType 8 | from types import LongType 9 | 10 | from Peach.transformer import Transformer 11 | 12 | 13 | class NumberToString(Transformer): 14 | """Transforms any type of number (int, long, float) to string.""" 15 | 16 | def __init__(self, formatString=None): 17 | """Create NumberToString Instance. 18 | |formatString| is a standard Python string formatter (optional). 19 | """ 20 | Transformer.__init__(self) 21 | self._formatString = formatString 22 | 23 | def realEncode(self, data): 24 | """Convert number to string. 25 | If no formatString was specified in class constructor data type is 26 | dynamically determined and converted using a default formatString of 27 | "%d", "%f", or "%d" for Int, Float, and Long respectively. 28 | """ 29 | 30 | if self._formatString is None: 31 | retType = type(data) 32 | if retType is IntType: 33 | return "%d" % data 34 | elif retType is FloatType: 35 | return "%f" % data 36 | elif retType is LongType: 37 | return "%d" % data 38 | else: 39 | return data 40 | 41 | return self._formatString % data 42 | 43 | 44 | class SignedNumberToString(Transformer): 45 | """Transforms unsigned numbers to strings.""" 46 | 47 | def __init__(self): 48 | Transformer.__init__(self) 49 | self._size = None 50 | 51 | def realEncode(self, data): 52 | self._size = size = len(data) * 8 53 | 54 | if size == 0: 55 | return "" 56 | elif size == 8: 57 | return str(unpack("b", data)[0]) 58 | elif size == 16: 59 | return str(unpack("h", data)[0]) 60 | elif size == 24: 61 | raise Exception("24 bit numbers not supported") 62 | elif size == 32: 63 | return str(unpack("i", data)[0]) 64 | elif size == 64: 65 | return str(unpack("q", data)[0]) 66 | 67 | raise Exception("Unknown numerical size") 68 | 69 | def realDecode(self, data): 70 | size = self._size 71 | 72 | if size is None: 73 | return "" 74 | 75 | if size == 8: 76 | return pack("b", data) 77 | elif size == 16: 78 | return pack("h", data) 79 | elif size == 24: 80 | raise Exception("24 bit numbers not supported") 81 | elif size == 32: 82 | return pack("i", data) 83 | elif size == 64: 84 | return pack("q", data) 85 | 86 | raise Exception("Unknown numerical size") 87 | 88 | 89 | class UnsignedNumberToString(Transformer): 90 | """Transforms unsigned numbers to strings.""" 91 | 92 | def __init__(self): 93 | Transformer.__init__(self) 94 | self._size = None 95 | 96 | def realEncode(self, data): 97 | self._size = size = len(data) * 8 98 | 99 | if size == 0: 100 | return "" 101 | elif size == 8: 102 | return str(unpack("B", data)[0]) 103 | elif size == 16: 104 | return str(unpack("H", data)[0]) 105 | elif size == 24: 106 | raise Exception("24 bit numbers not supported") 107 | elif size == 32: 108 | return str(unpack("I", data)[0]) 109 | elif size == 64: 110 | return str(unpack("Q", data)[0]) 111 | 112 | raise Exception("Unknown numerical size:") 113 | 114 | def realDecode(self, data): 115 | size = self._size 116 | 117 | if size is None: 118 | return "" 119 | 120 | if size == 8: 121 | return pack("B", data) 122 | elif size == 16: 123 | return pack("H", data) 124 | elif size == 24: 125 | raise Exception("24 bit numbers not supported") 126 | elif size == 32: 127 | return pack("I", data) 128 | elif size == 64: 129 | return pack("Q", data) 130 | 131 | raise Exception("Unknown numerical size") 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://github.com/posidron/posidron.github.io/raw/master/static/images/peach.png) 2 | 3 | MozPeach is a fork of [Peach v2.7](http://www.peachfuzzer.com) by Mozilla Security. With support from our community and partnerships our goal is to continue to deliver Peach as an open source product with Python compatibility and new features. 4 | 5 | Our focus is on usability, speed and fewer dependencies. We have also begun work on Python 3 support, replaced deprecated Python dependencies, switched the XML back-end, added a new configuration system, simplified code and much more. 6 | 7 | ### Setup 8 | 9 | ##### Prerequisites for Ubuntu 10 | ``` 11 | sudo apt-get --yes --quiet install libxml2-dev libxslt1-dev lib32z1-dev 12 | ``` 13 | 14 | ##### General 15 | ```bash 16 | pip install virtualenv 17 | pip install virtualenvwrapper 18 | 19 | git clone --depth 1 https://github.com/mozillasecurity/peach 20 | 21 | cd peach 22 | git clone --depth 1 https://github.com/mozillasecurity/fuzzdata 23 | 24 | mkvirtualenv -r requirements.txt peach 25 | ``` 26 | 27 | or 28 | 29 | ```bash 30 | workon peach 31 | ``` 32 | 33 | ### Fundamentals 34 | Peach uses XML based "pits" as configuration files. There are two types of pits which we will briefly describe here. 35 | 36 | **Pit: Data Model** 37 | 38 | A data-model pit is an XML description of a specification and is required to parse any kind of input into an in-memory XML tree. Peach then uses that tree to generate fuzzed output. 39 | 40 | **Pit: Target** 41 | 42 | The target pit is used to define how the target process will get fuzzed, how it will be monitored for suspicious behavior and how to deal with results. 43 | 44 | It is optional whether you place everything into one pit however not doing so will simplify working with multiple targets, different hosts and reusing pits. Following the data model/target pit practice will allow the reuse of data model pits across projects. 45 | 46 | 47 | ### Examples 48 | 49 | ##### Run 50 | ```bash 51 | ./peach.py -pit Pits///.xml -target Pits/Targets/firefox.xml -run Browser 52 | ``` 53 | 54 | **HINT**: You can set related configuration values for both pits from the command-line by using the -macros switch. 55 | 56 | ##### Debug 57 | ```bash 58 | ./peach.py -pit Pits///.xml -1 -debug | less -R 59 | ``` 60 | 61 | **NOTE**: This will show a very verbose output of the parsing process. To see only the results of the parsing process for each element you can add: "| grep Rating | less -R" 62 | 63 | 64 | ### Help Menu 65 | ``` 66 | % ./peach.py -h 67 | usage: peach.py [-h] [-pit path] [-run name] 68 | [-analyzer ANALYZER [ANALYZER ...]] [-parser PARSER] 69 | [-target TARGET] [-macros MACROS [MACROS ...]] [-seed #] 70 | [-debug] [-new] [-1] [-range # #] [-test] [-count] [-skipto #] 71 | [-parallel # #] [-agent # #] [-logging #] 72 | [-check model samples] [-verbose] [-clean] [-version] 73 | 74 | Peach Runtime 75 | 76 | optional arguments: 77 | -h, --help show this help message and exit 78 | -pit path pit file 79 | -run name run name 80 | -analyzer ANALYZER [ANALYZER ...] 81 | load analyzer. 82 | -parser PARSER use specific parser. 83 | -target TARGET select a target pit. 84 | -macros MACROS [MACROS ...] 85 | override configuration macros 86 | -seed # seed 87 | -debug turn on debugging. (default: False) 88 | -new use new relations. 89 | -1 run single test case. 90 | -range # # run range of test cases. 91 | -test validate pit file. 92 | -count count test cases for deterministic strategies. 93 | -skipto # skip to a test case number. 94 | -parallel # # use parallelism. 95 | -agent # # start agent. 96 | -logging # verbosity level of logging 97 | -check model samples validate a data model against a set of samples. 98 | -verbose turn verbosity on. (default: False) 99 | -clean remove python object files. 100 | -version show program's version number and exit 101 | ``` 102 | 103 | ### Resources 104 | 105 | Resources which aid in building a pit based on the grammar of a file format: 106 | 107 | * http://www.sweetscape.com/010editor/templates/ 108 | * http://www.synalysis.net/formats.xml 109 | -------------------------------------------------------------------------------- /Peach/Mutators/datatree.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.mutator import * 5 | from Peach.Engine.common import * 6 | 7 | 8 | class DataTreeRemoveMutator(Mutator): 9 | """ 10 | Remove nodes from data tree. 11 | """ 12 | 13 | def __init__(self, peach, node): 14 | Mutator.__init__(self) 15 | self.isFinite = True 16 | self.name = "DataTreeRemoveMutator" 17 | self._peach = peach 18 | 19 | def next(self): 20 | raise MutatorCompleted() 21 | 22 | def getCount(self): 23 | return 1 24 | 25 | @staticmethod 26 | def supportedDataElement(e): 27 | if isinstance(e, DataElement) and e.isMutable: 28 | return True 29 | return False 30 | 31 | def sequentialMutation(self, node): 32 | self.changedName = node.getFullnameInDataModel() 33 | node.setValue("") 34 | 35 | def randomMutation(self, node, rand): 36 | self.changedName = node.getFullnameInDataModel() 37 | node.setValue("") 38 | 39 | 40 | class DataTreeDuplicateMutator(Mutator): 41 | """ 42 | Duplicate a node's value starting at 2x through 50x. 43 | """ 44 | 45 | def __init__(self, peach, node): 46 | Mutator.__init__(self) 47 | self.isFinite = True 48 | self.name = "DataTreeDuplicateMutator" 49 | self._peach = peach 50 | self._cnt = 2 51 | self._maxCount = 50 52 | 53 | def next(self): 54 | self._cnt += 1 55 | if self._cnt > self._maxCount: 56 | raise MutatorCompleted() 57 | 58 | def getCount(self): 59 | return self._maxCount 60 | 61 | @staticmethod 62 | def supportedDataElement(e): 63 | if isinstance(e, DataElement) and e.isMutable: 64 | return True 65 | return False 66 | 67 | def sequentialMutation(self, node): 68 | self.changedName = node.getFullnameInDataModel() 69 | node.setValue(node.getValue() * self._cnt) 70 | 71 | def randomMutation(self, node, rand): 72 | self.changedName = node.getFullnameInDataModel() 73 | count = rand.randint(0, self._cnt) 74 | node.setValue(node.getValue() * count) 75 | 76 | 77 | class DataTreeSwapNearNodesMutator(Mutator): 78 | """ 79 | Swap two nodes in the data model that are near each other. 80 | TODO: Actually move the nodes instead of just the data. 81 | """ 82 | 83 | def __init__(self, peach, node): 84 | Mutator.__init__(self) 85 | self.isFinite = True 86 | self.name = "DataTreeSwapNearNodesMutator" 87 | self._peach = peach 88 | 89 | def next(self): 90 | raise MutatorCompleted() 91 | 92 | def getCount(self): 93 | return 1 94 | 95 | def _moveNext(self, currentNode): 96 | # Check if we are top dogM 97 | if currentNode.parent is None or \ 98 | not isinstance(currentNode.parent, DataElement): 99 | return None 100 | # Get sibling 101 | foundCurrent = False 102 | for node in currentNode.parent: 103 | if node == currentNode: 104 | foundCurrent = True 105 | continue 106 | if foundCurrent and isinstance(node, DataElement): 107 | return node 108 | # Get sibling of parentM 109 | return self._moveNext(currentNode.parent) 110 | 111 | def _nextNode(self, node): 112 | nextNode = None 113 | # Walk down node tree 114 | for child in node._children: 115 | if isinstance(child, DataElement): 116 | nextNode = child 117 | break 118 | # Walk over or up if we can 119 | if nextNode is None: 120 | nextNode = self._moveNext(node) 121 | return nextNode 122 | 123 | @staticmethod 124 | def supportedDataElement(e): 125 | if isinstance(e, DataElement) and e.isMutable: 126 | return True 127 | return False 128 | 129 | def sequentialMutation(self, node): 130 | self.changedName = node.getFullnameInDataModel() 131 | nextNode = self._nextNode(node) 132 | if nextNode is not None: 133 | v1 = node.getValue() 134 | v2 = nextNode.getValue() 135 | node.setValue(v2) 136 | nextNode.setValue(v1) 137 | 138 | def randomMutation(self, node, rand): 139 | self.changedName = node.getFullnameInDataModel() 140 | self.sequentialMutation(node) 141 | -------------------------------------------------------------------------------- /Peach/Publishers/com.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import os 5 | import sys 6 | import time 7 | import signal 8 | try: 9 | import pywintypes 10 | import win32com.client 11 | import win32com.client.gencache 12 | except: 13 | pass 14 | 15 | from Peach.publisher import Publisher 16 | 17 | 18 | class Com(Publisher): 19 | """ 20 | Very simple Com publisher that allows for a single method call. The 21 | method call is fed in as a string which is evaled. This allows for 22 | calling any method with any number of parameters. 23 | """ 24 | 25 | _clsid = None 26 | _methodFormat = None 27 | _lastReturn = None 28 | _object = None 29 | 30 | def __init__(self, clsid): 31 | """ 32 | Create Com Object. clsid = '{...}' 33 | 34 | @type clsid: string 35 | @param clsid: CLSID of COM object in {...} format 36 | """ 37 | Publisher.__init__(self) 38 | self._clsid = clsid 39 | self.withNode = True 40 | 41 | def start(self): 42 | try: 43 | self._object = None 44 | self._object = win32com.client.DispatchEx(self._clsid) 45 | 46 | except pywintypes.com_error as e: 47 | print("Caught pywintypes.com_error creating ActiveX control [%s]" % e) 48 | raise 49 | 50 | except: 51 | print("Caught unkown exception creating ActiveX control [%s]" % sys.exc_info()[0]) 52 | raise 53 | 54 | def stop(self): 55 | self._object = None 56 | 57 | # def call(self, method, args): 58 | def callWithNode(self, method, args, argNodes): 59 | """ 60 | Call method on COM object. 61 | 62 | @type method: string 63 | @param method: Name of method to invoke 64 | @type args: array of objects 65 | @param args: Arguments to pass 66 | """ 67 | 68 | self._lastReturn = None 69 | 70 | realArgNodes = [] 71 | for arg in argNodes: 72 | if len(arg) == 1: 73 | realArgNodes.append(arg[0]) 74 | else: 75 | realArgNodes.append(arg) 76 | 77 | for arg in realArgNodes: 78 | print("Type", type(arg.getInternalValue())) 79 | print("Value", repr(arg.getInternalValue())) 80 | 81 | try: 82 | ret = None 83 | callStr = "ret = self._object.%s(" % str(method) 84 | 85 | if len(realArgNodes) > 0: 86 | for i in range(0, len(argNodes)): 87 | callStr += "realArgNodes[%d].getInternalValue()," % i 88 | 89 | callStr = callStr[:len(callStr) - 1] + ")" 90 | 91 | else: 92 | callStr += ")" 93 | 94 | print("Call:", callStr) 95 | 96 | exec(callStr) 97 | return ret 98 | 99 | except pywintypes.com_error as e: 100 | print("Caught pywintypes.com_error on call [%s]" % e) 101 | raise 102 | 103 | except NameError as e: 104 | print("Caught NameError on call [%s]" % e) 105 | raise 106 | 107 | except: 108 | print("Com::Call(): Caught unknown exception") 109 | raise 110 | 111 | # def property(self, property, value = None): 112 | def propertyWithNode(self, property, value, valueNode): 113 | """ 114 | Get or set property 115 | 116 | @type property: string 117 | @param property: Name of method to invoke 118 | @type value: object 119 | @param value: Value to set. If None, return property instead 120 | """ 121 | 122 | try: 123 | if value is None: 124 | ret = None 125 | callStr = "ret = self._object.%s" % str(property) 126 | 127 | #print "Call string:", callStr 128 | exec(callStr) 129 | return ret 130 | 131 | ret = None 132 | callStr = "self._object.%s = valueNode.getInternalValue()" % str(property) 133 | 134 | #print "Call string:", callStr 135 | exec(callStr) 136 | return None 137 | 138 | except pywintypes.com_error as e: 139 | print("Caught pywintypes.com_error on property [%s]" % e) 140 | #raise 141 | 142 | except NameError as e: 143 | print("Caught NameError on property [%s]" % e) 144 | #raise 145 | 146 | except: 147 | print("Com::property(): Caught unknown exception") 148 | raise 149 | 150 | return None 151 | -------------------------------------------------------------------------------- /Peach/Generators/unicode.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach import generator 5 | from Peach.Generators.dictionary import * 6 | from Peach.Generators.static import * 7 | 8 | 9 | class GoodUnicode(generator.SimpleGenerator): 10 | 11 | _generator = Static('PLACE HOLDER') 12 | 13 | def __init__(self, group, generator): 14 | """ 15 | @type group: Group 16 | @param group: Group to use 17 | """ 18 | 19 | Generator.__init__(self) 20 | self.setGroup(group) 21 | 22 | 23 | class BadUnicode(generator.SimpleGenerator): 24 | 25 | _generator = Static('PLACE HOLDER') 26 | 27 | def __init__(self, group, generator): 28 | """ 29 | @type group: Group 30 | @param group: Group to use 31 | """ 32 | 33 | Generator.__init__(self) 34 | self.setGroup(group) 35 | 36 | 37 | class OverLongUtf8(generator.Generator): 38 | """ 39 | This generator creates overlong UTF-8 encodings. First output is correct 40 | notation, then on each generation we perform a longer encoding of each 41 | character until we can do no more. 42 | 43 | NOTE: Only supports ascii chars under 127 right now :/ 44 | """ 45 | 46 | _data = None 47 | _size = 1 48 | _maxSize = 6 49 | _emptyByte = 0x80 50 | _start2 = 0xC0 51 | _start3 = 0xE0 52 | _start4 = 0xF0 53 | _start5 = 0xF8 54 | _start6 = 0xFC 55 | _firstMask = 0xC0 56 | _lastMask = 0x80 57 | 58 | def __init__(self, group, data): 59 | """ 60 | @type group: Group 61 | @param group: Group to use 62 | @type data: Generator 63 | @param data: Data to perform UTF-8 encoding on 64 | """ 65 | self.setGroup(group) 66 | self._data = data 67 | 68 | def next(self): 69 | self._size += 1 70 | if self._size > self._maxSize: 71 | raise generator.GeneratorCompleted("OverLongUtf8") 72 | 73 | def getRawValue(self): 74 | data = self._data.getValue() 75 | ret = '' 76 | 77 | if self._size == 1: 78 | return data 79 | 80 | elif self._size == 2: 81 | for c in data: 82 | ret += "%c%c" % (self._start2, self._lastMask | ord(c)) 83 | 84 | elif self._size == 3: 85 | for c in data: 86 | ret += "%c%c%c" % (self._start3, self._emptyByte, 87 | self._lastMask | ord(c)) 88 | 89 | elif self._size == 4: 90 | for c in data: 91 | ret += "%c%c%c%c" % (self._start4, self._emptyByte, 92 | self._emptyByte, self._lastMask | ord(c)) 93 | 94 | elif self._size == 5: 95 | for c in data: 96 | ret += "%c%c%c%c%c" % (self._start5, self._emptyByte, 97 | self._emptyByte, self._emptyByte, 98 | self._lastMask | ord(c)) 99 | 100 | elif self._size == 6: 101 | for c in data: 102 | ret += "%c%c%c%c%c%c" % (self._start6, self._emptyByte, 103 | self._emptyByte, self._emptyByte, 104 | self._emptyByte, self._lastMask | ord(c)) 105 | 106 | return ret 107 | 108 | def reset(self): 109 | self._size = 1 110 | 111 | @staticmethod 112 | def unittest(): 113 | expected1 = "%c" % 0x0A 114 | expected2 = "%c%c" % (0xC0, 0x8A) 115 | expected3 = "%c%c%c" % (0xE0, 0x80, 0x8A) 116 | expected4 = "%c%c%c%c" % (0xF0, 0x80, 0x80, 0x8A) 117 | expected5 = "%c%c%c%c%c" % (0xF8, 0x80, 0x80, 0x80, 0x8A) 118 | expected6 = "%c%c%c%c%c%c" % (0xFC, 0x80, 0x80, 0x80, 0x80, 0x8A) 119 | 120 | g = OverLongUtf8(None, Static("%c" % 0x0A)) 121 | 122 | if g.getRawValue() != expected1: 123 | print("OverLongUtf8 unittest failure 1") 124 | g.next() 125 | if g.getRawValue() != expected2: 126 | print("OverLongUtf8 unittest failure 2") 127 | g.next() 128 | if g.getRawValue() != expected3: 129 | print("OverLongUtf8 unittest failure 3") 130 | g.next() 131 | if g.getRawValue() != expected4: 132 | print("OverLongUtf8 unittest failure 4") 133 | g.next() 134 | if g.getRawValue() != expected5: 135 | print("OverLongUtf8 unittest failure 5") 136 | g.next() 137 | if g.getRawValue() != expected6: 138 | print("OverLongUtf8 unittest failure 6") 139 | print("Done with OverLongUtf8 unittests") 140 | -------------------------------------------------------------------------------- /Peach/Transformers/Type/Integer.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from struct import pack 5 | from struct import unpack 6 | 7 | from Peach.transformer import Transformer 8 | 9 | 10 | class _AsNumber(Transformer): 11 | """Base class to transform a number to a specific size.""" 12 | 13 | def __init__(self, isSigned=1, isLittleEndian=1): 14 | """ 15 | :param isSigned: 1 for signed, 0 for unsigned 16 | :type isSigned: number 17 | :param isLittleEndian: 1 for signed, 0 for unsigned 18 | :type isLittleEndian: number 19 | """ 20 | 21 | Transformer.__init__(self) 22 | self._isSigned = isSigned 23 | self._isLittleEndian = isLittleEndian 24 | 25 | def _unfuglyNumber(self, data): 26 | """ 27 | Will attempt to figure out if the incoming data is a byte stream that 28 | must be converted to get our number we will then cast, due to 29 | StaticBinary issues. 30 | """ 31 | try: 32 | if int(data) == int(str(data)): 33 | return int(data) 34 | except Exception as e: 35 | pass 36 | hexString = "" 37 | for c in data: 38 | h = hex(ord(c))[2:] 39 | if len(h) < 2: 40 | h = "0" + h 41 | hexString += h 42 | return int(hexString, 16) 43 | 44 | def realEncode(self, data): 45 | data = self._unfuglyNumber(data) 46 | packStr = '' 47 | if self._isLittleEndian == 1: 48 | packStr = '<' 49 | else: 50 | packStr = '>' 51 | if self._isSigned == 1: 52 | packStr += self._packFormat.lower() 53 | else: 54 | packStr += self._packFormat.upper() 55 | # Prevent silly deprecation warnings from Python 56 | if packStr[1] == 'b' and data > 0xfe: 57 | data = 0 58 | elif packStr[1] == 'B' and (data > 0xff or data < 0): 59 | data = 0 60 | elif packStr[1] == 'h' and data > 0xfffe: 61 | data = 0 62 | elif packStr[1] == 'H' and (data > 0xffff or data < 0): 63 | data = 0 64 | elif packStr[1] == 'i' and data > 0xfffffffe: 65 | data = 0 66 | elif packStr[1] == 'L' and (data > 0xffffffff or data < 0): 67 | data = 0 68 | elif packStr[1] == 'q' and data > 0xfffffffffffffffe: 69 | data = 0 70 | elif packStr[1] == 'Q' and (data > 0xffffffffffffffff or data < 0): 71 | data = 0 72 | try: 73 | return pack(packStr, int(data)) 74 | except Exception as e: 75 | return pack(packStr, 0) 76 | 77 | def realDecode(self, data): 78 | packStr = '' 79 | if self._isLittleEndian == 1: 80 | packStr = '<' 81 | else: 82 | packStr = '>' 83 | if self._isSigned == 1: 84 | packStr += self._packFormat.lower() 85 | else: 86 | packStr += self._packFormat.upper() 87 | try: 88 | return unpack(packStr, data)[0] 89 | except Exception as e: 90 | return 0 91 | 92 | 93 | class AsInt8(_AsNumber): 94 | """Transform an number to an Int8 or UInt8.""" 95 | _packFormat = 'b' 96 | 97 | 98 | class AsInt16(_AsNumber): 99 | """Transform an number to an Int16 or UInt16.""" 100 | _packFormat = 'h' 101 | 102 | 103 | class AsInt24(Transformer): 104 | """Transform an number to a UInt24.""" 105 | 106 | def __init__(self, isSigned=1, isLittleEndian=1): 107 | """ 108 | @param isSigned: 1 for signed, 0 for unsigned (we ignore this) 109 | @type isSigned: number 110 | @param isLittleEndian: 1 for signed, 0 for unsigned 111 | @type isLittleEndian: number 112 | """ 113 | Transformer.__init__(self) 114 | self._isLittleEndian = isLittleEndian 115 | 116 | def realEncode(self, data): 117 | try: 118 | data = int(data) 119 | packStr = '' 120 | if self._isLittleEndian == 1: 121 | packStr = '<' 122 | else: 123 | packStr = '>' 124 | packStr += 'L' 125 | v = pack(packStr, data) 126 | if self._isLittleEndian == 1: 127 | return v[:3] 128 | else: 129 | return v[1:] 130 | except Exception as e: 131 | return '0' 132 | 133 | 134 | class AsInt32(_AsNumber): 135 | """Transform an number to an Int32 or UInt32.""" 136 | _packFormat = 'l' 137 | 138 | 139 | class AsInt64(_AsNumber): 140 | """Transform an number to an Int64 or UInt64.""" 141 | _packFormat = 'q' 142 | -------------------------------------------------------------------------------- /Pits/Targets/Firefox/firefox.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Peach/Publishers/smtp.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import time 5 | import sys 6 | import smtplib 7 | 8 | from email.mime.multipart import MIMEMultipart 9 | from email.mime.base import MIMEBase 10 | from email.mime.text import MIMEText 11 | from email import Encoders 12 | from email.Utils import COMMASPACE, formatdate 13 | from Peach.publisher import Publisher 14 | from Peach.Engine.common import PeachException 15 | 16 | 17 | class SMTPPublisher(Publisher): 18 | def __init__(self, host="localhost", 19 | port=smtplib.SMTP_PORT, 20 | debugLevel=0, 21 | mailFrom="localhost@localhost", 22 | mailTo="localhost@localhost", 23 | username="", 24 | password=""): 25 | Publisher.__init__(self) 26 | self.host = host 27 | try: 28 | self.port = int(port) 29 | except: 30 | raise PeachException("The SMTP publisher parameter for port is not a valid number.") 31 | self.debugLevel = int(debugLevel) 32 | self.mailFrom = mailFrom 33 | self.mailTo = mailTo 34 | self.username = username 35 | self.password = password 36 | self.loadBalance = 0 37 | self._connected = None 38 | 39 | def start(self): 40 | if self._connected: 41 | return 42 | 43 | print("[*] Connecting to %s:%d ..." % (self.host, self.port)) 44 | try: 45 | self.smtp = smtplib.SMTP(self.host, self.port) 46 | except: 47 | raise PeachException("Peer %s:%d is down or connection settings are wrong." % (self.host, self.port)) 48 | self._connected = True 49 | self.smtp.set_debuglevel(self.debugLevel) 50 | 51 | def connect(self): 52 | pass 53 | 54 | def send(self, data): 55 | if not self.loadBalance % 500 and self.loadBalance != 0: 56 | print("[*] Pause ...") 57 | time.sleep(10) 58 | for i in range(3): 59 | try: 60 | self.smtp.sendmail(self.mailFrom, self.mailTo, data) 61 | exception = None 62 | break 63 | except: 64 | exception = sys.exc_info() 65 | time.sleep(5) 66 | if exception: 67 | reason = "" 68 | try: 69 | reason = str(exception[1]) 70 | except: 71 | reason = "unknown reason." 72 | message = "SMTP send mail to %s:%d failed: %s" % (self.host, self.port, reason) 73 | if message.find("5.4.0") > -1: 74 | print(message) 75 | else: 76 | self.smtp.close() 77 | raise PeachException(message) 78 | self.loadBalance += 1 79 | 80 | def close(self): 81 | pass 82 | 83 | def stop(self): 84 | pass 85 | 86 | 87 | class EmailAttachment(Publisher): 88 | """ 89 | Send fuzzed data as email attachment. 90 | """ 91 | 92 | def __init__(self, server, fileName, msgTo, msgFrom="peach@peach.org", 93 | msgSubject="Fuzzing Test", 94 | msgText="Message generated by Peach Fuzzing Platform.\n\nhttp://peachfuzzer.com\n\n - Peach\n"): 95 | Publisher.__init__(self) 96 | self.server = server 97 | self.fileName = fileName 98 | self.msgFrom = msgFrom 99 | self.msgTo = msgTo 100 | self.msgSubject = msgSubject 101 | self.msgText = msgText 102 | 103 | def send(self, data): 104 | """ 105 | Publish some data 106 | 107 | @type data: string 108 | @param data: Data to publish 109 | """ 110 | # Build Message Body 111 | msg = MIMEMultipart() 112 | msg['From'] = self.msgFrom 113 | msg['To'] = self.msgTo 114 | msg['Date'] = formatdate(localtime=True) 115 | msg['Subject'] = self.msgSubject 116 | msg.attach(MIMEText(self.msgText)) 117 | # Attach file 118 | part = MIMEBase('application', 'pdf') 119 | part.set_payload(data) 120 | Encoders.encode_base64(part) 121 | part.add_header('Content-Disposition', 'attachment; filename="%s"' % self.fileName) 122 | msg.attach(part) 123 | # Send email 124 | smtp = smtplib.SMTP(self.server) 125 | smtp.sendmail(self.msgFrom, self.msgTo, msg.as_string()) 126 | smtp.close() 127 | 128 | def connect(self): 129 | """ 130 | Called to connect or open a connection/file. 131 | """ 132 | pass 133 | 134 | def close(self): 135 | """ 136 | Close current stream/connection. 137 | """ 138 | pass 139 | 140 | 141 | class _OleStorage(object): 142 | """ 143 | This class wraps OLE Storage APIs 144 | """ 145 | pass 146 | -------------------------------------------------------------------------------- /Peach/Agent/gui.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | try: 5 | 6 | import win32gui, win32con 7 | import sys, time, os, signal 8 | from threading import * 9 | from Peach.agent import * 10 | 11 | class _WindowWatcher(Thread): 12 | """ 13 | Look one child deep on each top level window to try 14 | and locate dialog boxen. 15 | """ 16 | 17 | def __init__(self): 18 | Thread.__init__(self) 19 | 20 | self.CloseWindows = False 21 | self.FoundWindowEvent = None # Will be Event() 22 | self.WindowNames = None # Will be [] 23 | self.StopEvent = None # Will be Event() 24 | 25 | @staticmethod 26 | def enumCallback(hwnd, self): 27 | title = win32gui.GetWindowText(hwnd) 28 | 29 | for name in self.WindowNames: 30 | if title.find(name) > -1: 31 | try: 32 | self.FoundWindowEvent.set() 33 | 34 | if self.CloseWindows: 35 | win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0) 36 | except: 37 | pass 38 | else: 39 | try: 40 | win32gui.EnumChildWindows(hwnd, _WindowWatcher.enumChildCallback, self) 41 | except: 42 | pass 43 | 44 | return True 45 | 46 | @staticmethod 47 | def enumChildCallback(hwnd, self): 48 | title = win32gui.GetWindowText(hwnd) 49 | 50 | for name in self.WindowNames: 51 | if title.find(name) > -1: 52 | try: 53 | self.FoundWindowEvent.set() 54 | 55 | if self.CloseWindows: 56 | win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0) 57 | except: 58 | pass 59 | 60 | return True 61 | 62 | def run(self): 63 | while not self.StopEvent.isSet(): 64 | win32gui.EnumWindows(_WindowWatcher.enumCallback, self) 65 | time.sleep(.2) 66 | 67 | 68 | class PopupWatcher(Monitor): 69 | """ 70 | Will watch for specific dialogs and optionally kill 71 | or log a fault when detected. 72 | """ 73 | 74 | def __init__(self, args): 75 | """ 76 | Constructor. Arguments are supplied via the Peach XML 77 | file. 78 | 79 | @type args: Dictionary 80 | @param args: Dictionary of parameters 81 | """ 82 | 83 | # Our name for this monitor 84 | self._name = "PopupWatcher" 85 | self._closeWindows = False 86 | self._triggerFaults = False 87 | 88 | if args.has_key("CloseWindows"): 89 | if args["CloseWindows"].replace("'''", "").lower() in ["yes", "true", "1"]: 90 | self._closeWindows = True 91 | 92 | if args.has_key("TriggerFaults"): 93 | if args["TriggerFaults"].replace("'''", "").lower() in ["yes", "true", "1"]: 94 | self._triggerFaults = True 95 | 96 | if not args.has_key("WindowNames"): 97 | raise Exception("PopupWatcher requires a parameter named WindowNames.") 98 | 99 | self._names = args["WindowNames"].replace("'''", "").split(',') 100 | 101 | def OnTestStarting(self): 102 | """ 103 | Called right before start of test case or variation 104 | """ 105 | 106 | self._thread = _WindowWatcher() 107 | 108 | self._thread.CloseWindows = self._closeWindows 109 | self._thread.FoundWindowEvent = Event() 110 | self._thread.WindowNames = self._names 111 | self._thread.StopEvent = Event() 112 | 113 | self._thread.start() 114 | 115 | def OnTestFinished(self): 116 | """ 117 | Called right after a test case or variation 118 | """ 119 | self._thread.StopEvent.set() 120 | time.sleep(.6) 121 | 122 | def DetectedFault(self): 123 | """ 124 | Check if a fault was detected. 125 | """ 126 | if self._triggerFaults: 127 | return self._thread.FoundWindowEvent.isSet() 128 | 129 | return False 130 | 131 | def OnShutdown(self): 132 | """ 133 | Called when Agent is shutting down, typically at end 134 | of a test run or when a Stop-Run occurs 135 | """ 136 | try: 137 | self._thread.StopEvent.set() 138 | time.sleep(.6) 139 | except: 140 | pass 141 | 142 | except: 143 | pass 144 | -------------------------------------------------------------------------------- /Peach/Publishers/usb.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import os 5 | import sys 6 | import struct 7 | 8 | from Peach.publisher import Publisher 9 | from Peach.Engine.common import PeachException 10 | 11 | 12 | try: 13 | import usb.core 14 | import usb.util 15 | except ImportError: 16 | print("Please install the following dependencies:") 17 | 18 | if sys.platform == "darwin": 19 | sys.exit("sudo port install libusb py27-pyusb-devel") 20 | 21 | if sys.platform == "linux2": 22 | sys.exit("sudo apt-get install libusb-1.0-0 python-usb") 23 | 24 | if sys.platform == "nt": 25 | sys.exit("PyUSB: http://sourceforge.net/apps/trac/pyusb/\r\n" 26 | "LibUSB: http://sourceforge.net/apps/trac/libusb-win32/") 27 | 28 | 29 | def getDevice(idVendor, idProduct): 30 | busses = usb.busses() 31 | for i, dev in enumerate(busses[0].devices): 32 | if dev.idProduct == idProduct and dev.idVendor == idVendor: 33 | return busses[0].devices[i] 34 | 35 | 36 | def getDeviceName(handle): 37 | name = [] 38 | for index in range(1, 3): 39 | name.append(handle.getString(index, 32)) 40 | return " ".join(name) 41 | 42 | 43 | def getConfiguration(dev): 44 | return dev.configurations[0] 45 | 46 | 47 | def getInterface(dev): 48 | conf = getConfiguration(dev) 49 | return conf.interfaces[0][0] 50 | 51 | 52 | def getEndpoints(dev): 53 | endpoints = [] 54 | for endpoint in getInterface(dev): 55 | endpoints.append(endpoint) 56 | return endpoints 57 | 58 | 59 | def send_controlMsg(handle, command): 60 | try: 61 | #handle.controlMsg(0xA1, 1, 0) 62 | handle.controlMsg(usb.TYPE_CLASS, 0x9, [0x01, command, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 0x200, 2000) 63 | except usb.USBError as e: 64 | print(e) 65 | 66 | 67 | def send_interruptWrite(handle): 68 | try: 69 | handle.interruptWrite(0x81, [0] * 64, 1000) 70 | except: 71 | pass 72 | 73 | 74 | def send_ctrl_transfer(): 75 | dev = usb.core.find(idVendor=0x05ac, idProduct=0x12a0) 76 | dev.set_configuration() 77 | dev.ctrl_transfer(0x80, 6, 0x100, 1, [0x40, 1, 3], 1000) 78 | 79 | 80 | class USBCtrlTransfer(Publisher): 81 | def __init__(self, idVendor, idProduct): 82 | Publisher.__init__(self) 83 | 84 | self.idVendor = int(idVendor, 16) 85 | self.idProduct = int(idProduct, 16) 86 | 87 | self.dev = None 88 | 89 | def start(self): 90 | #if self.dev: 91 | # return 92 | 93 | self.dev = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) 94 | 95 | if not self.dev: 96 | raise PeachException("Device not found.") 97 | 98 | try: 99 | self.dev.set_configuration() 100 | except usb.core.USBError as e: 101 | raise PeachException(str(e)) 102 | 103 | def send(self, data): 104 | try: 105 | bmRequestType, bRequest, wValue, wIndex, wLength = struct.unpack(">BBHHH", data) 106 | except: 107 | pass 108 | else: 109 | try: 110 | self.dev.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, timeout=1000) 111 | except: 112 | pass 113 | 114 | def connect(self): 115 | pass 116 | 117 | def close(self): 118 | pass 119 | 120 | 121 | if __name__ == "__main__": 122 | 123 | if sys.platform == "darwin": 124 | os.system("system_profiler SPUSBDataType") 125 | 126 | dev = getDevice(0x05ac, 0x12a0) 127 | usbConfiguration = getConfiguration(dev) 128 | usbInterface = getInterface(dev) 129 | 130 | handle = dev.open() 131 | 132 | print("\nDevice Name: {}\n".format(getDeviceName(handle))) 133 | 134 | try: 135 | prin("Unload current driver from interface ...") 136 | handle.detachKernelDriver(usbInterface.interfaceNumber) 137 | print("Done.") 138 | except usb.USBError as e: 139 | print("Failed!") 140 | 141 | try: 142 | print("Loading USB config ...") 143 | handle.setConfiguration(usbConfiguration.value) 144 | print("Done.") 145 | except usb.USBError as e: 146 | print("Failed!") 147 | 148 | try: 149 | print("Claiming interface ...") 150 | handle.claimInterface(usbInterface.interfaceNumber) 151 | print("Done.") 152 | except usb.USBError as e: 153 | print("Failed!") 154 | 155 | try: 156 | print("Set alternative interface ...") 157 | handle.setAltInterface(usbInterface.interfaceNumber) 158 | print("Done.") 159 | except usb.USBError as e: 160 | print("Failed!") 161 | 162 | try: 163 | print("Resetting device ...") 164 | handle.reset() 165 | print("Done.") 166 | except usb.USBError as e: 167 | print("Failed!") 168 | 169 | # ... 170 | 171 | try: 172 | print("Releasing interface ...") 173 | handle.releaseInterface() 174 | print("done.") 175 | except usb.USBError as e: 176 | print("failed!") -------------------------------------------------------------------------------- /Peach/Generators/repeater.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import static 5 | from Peach import generator, group 6 | from Peach.generator import Generator 7 | 8 | 9 | class Repeater(generator.Generator): 10 | """ 11 | Will repeat a value (generated by a Generator) by round count. Can be used 12 | for basic buffer overflow testing. 13 | 14 | Example: 15 | 16 | gen = Repeater(None, String("A"), 3) 17 | gen.getValue() 18 | A 19 | gen.next() 20 | gen.getValue() 21 | AA 22 | gen.next() 23 | gen.getValue() 24 | AAA 25 | 26 | Example: 27 | 28 | gen = Repeater(None, Static("Peach "), 5, 3) 29 | gen.getValue() 30 | Peach 31 | gen.next() 32 | gen.getValue() 33 | Peach Peach Peach Peach Peach 34 | gen.next() 35 | gen.getValue() 36 | Peach Peach Peach Peach Peach Peach Peach Peach Peach Peach 37 | """ 38 | 39 | def __init__(self, group, generator, incrementor=1, maxSteps=-1, 40 | startStep=None): 41 | """ 42 | @type group: Group 43 | @param group: Group this generator belongs to 44 | @type generator: Generator 45 | @param generator: Generator to repeat 46 | @type incrementor: number 47 | @param incrementor: Multiplier against round count 48 | @type maxSteps: number 49 | @param maxSteps: Maximum repeats 50 | @type startSteps: number 51 | @param startSteps: Start at this step 52 | """ 53 | Generator.__init__(self) 54 | self._incrementor = None 55 | self._roundCount = 1 56 | self._generator = None 57 | self._maxSteps = -1 58 | self._generator = generator 59 | self._incrementor = incrementor 60 | self.setGroup(group) 61 | self._maxSteps = maxSteps 62 | self._startStep = startStep 63 | if self._startStep is not None: 64 | self._roundCount = self._startStep 65 | 66 | def next(self): 67 | self._roundCount += 1 68 | if self._maxSteps != -1 and self._roundCount > self._maxSteps: 69 | self._roundCount -= 1 70 | raise generator.GeneratorCompleted("Peach.repeater.Repeater") 71 | 72 | def getRawValue(self): 73 | return str(self._generator.getValue()) * \ 74 | (self._roundCount * self._incrementor) 75 | 76 | def getGenerator(self): 77 | """ 78 | Get Generator who's value we will repeat. 79 | 80 | @rtype: Generator 81 | @return: Generator we are repeating 82 | """ 83 | return self._generator 84 | 85 | def setGenerator(self, generator): 86 | """ 87 | Set Generator who's value we will repeat. 88 | 89 | @type generator: Generator 90 | @param generator: Generator to repeate 91 | """ 92 | self._generator = generator 93 | 94 | def reset(self): 95 | self._roundCount = 1 96 | if self._startStep is not None: 97 | self._roundCount = self._startStep 98 | self._generator.reset() 99 | 100 | @staticmethod 101 | def unittest(): 102 | g = group.Group() 103 | r = Repeater(g, static.Static('A'), 1, 10) 104 | try: 105 | while g.next(): 106 | print(r.getValue()) 107 | except group.GroupCompleted: 108 | pass 109 | 110 | 111 | class RepeaterGI(generator.Generator): 112 | """ 113 | Will repeat a value (generated by a Generator) by multiplier (generator). 114 | 115 | Example: 116 | 117 | Repeater(None, String("A"), BadUnsignedNumbers(None)) 118 | 119 | Would produce a string of A's the length of each number returned by 120 | BadUnsignedNumbers. 121 | """ 122 | 123 | def __init__(self, group, generator, incrementor): 124 | """ 125 | @type group: Group 126 | @param group: Group this generator belongs to 127 | @type generator: Generator 128 | @param generator: Generator to repeat 129 | @type incrementor: Generator 130 | @param incrementor: Multiplier against round count 131 | """ 132 | Generator.__init__(self) 133 | self._incrementor = None 134 | self._roundCount = 1 135 | self._generator = None 136 | self._generator = generator 137 | self._incrementor = incrementor 138 | self.setGroup(group) 139 | 140 | def next(self): 141 | self._roundCount += 1 142 | self._incrementor.next() 143 | 144 | def getRawValue(self): 145 | try: 146 | ret = str(self._generator.getValue()) * \ 147 | int(self._incrementor.getValue()) 148 | except OverflowError: 149 | # Integer overflow exception. Oh well, we tried! 150 | ret = self._generator.getValue() 151 | except MemoryError: 152 | ret = self._generator.getValue() 153 | #print "RepeaterGI: MemoryError! Value is %d long multiplier is %d." % ( 154 | # len(str(ret)), int(self._incrementor.getValue())) 155 | return ret 156 | 157 | def reset(self): 158 | self._roundCount = 1 159 | self._incrementor.reset() 160 | self._generator.reset() 161 | -------------------------------------------------------------------------------- /Peach/Analyzers/stringtoken.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.Engine.dom import * 5 | from Peach.Engine.common import * 6 | from Peach.analyzer import * 7 | 8 | 9 | class StringTokenAnalyzer(Analyzer): 10 | """ 11 | Produces a tree of strings based on token precidence. 12 | """ 13 | 14 | supportCommandLine = False 15 | supportDataElement = True 16 | supportTopLevel = True 17 | 18 | # Tokens that group parts of a string 19 | pairs = [ 20 | [u'{', u'}'], 21 | [u'(', u')'], 22 | [u'[', u']'], 23 | [u'<', u'>'], 24 | ] 25 | 26 | # Tokens in order of precidence 27 | tokens = [ 28 | u'\0', u'\n', u'\r', u'<', u'>', u'?', u';', u',', u'|', u'@', u':', u'(', u')', 29 | u'{', u'}', u'[', u']', u'/', u'\\', u'&', u'=', u' ', u'-', u'+', u'.' 30 | ] 31 | 32 | def asCommandLine(self, args): 33 | """ 34 | Called when Analyzer is used from command line. 35 | Analyzer should produce PeachPit XML as output. 36 | """ 37 | raise NotImplementedError("StringTokenAnalyzer asCommandLine is not supported.") 38 | 39 | def asTopLevel(self, peach, args): 40 | """ 41 | Called when Analyzer is used from top level. 42 | From the top level producing zero or more data models and state models is possible. 43 | """ 44 | raise NotImplementedError("StringTokenAnalyzer asTopLevel is not supported.") 45 | 46 | def asDataElement(self, parent, args, data): 47 | """ 48 | Called when Analyzer is used in a data model. Should return a DataElement. 49 | """ 50 | if not isinstance(parent, String): 51 | raise PeachException("StringTokenAnalyzer can only be attached to elements.") 52 | self.stringType = parent.type 53 | dom = self.tokenize_string(data, None) 54 | if parent.nullTerminated: 55 | blob = Blob(None, None) 56 | blob.defaultValue = "\x00" if parent.type is "wchar" else "\x00\x00" 57 | dom.append(blob) 58 | # replace parent with new dom 59 | dom.name = parent.name 60 | i = parent.parent.index(parent) 61 | del parent.parent[parent.name] 62 | parent.parent.insert(i, dom) 63 | 64 | def _new_string_element(self, value, parent=None): 65 | s = String(None, parent) 66 | s.type = self.stringType 67 | s.defaultValue = value 68 | try: 69 | _ = int(s.defaultValue) 70 | hint = Hint("NumericalString", s) 71 | hint.value = "true" 72 | s.hints.append(hint) 73 | except ValueError: 74 | pass 75 | return s 76 | 77 | def _split(self, string, token): 78 | """ 79 | A version of split that also returns the tokens. 80 | """ 81 | # return re.split('(\%c)' % token, string) 82 | pos = string.find(token) 83 | parts = [] 84 | if pos is -1: 85 | return parts 86 | while pos > -1: 87 | parts.append(string[:pos]) 88 | parts.append(string[pos:pos + 1]) 89 | string = string[pos + 1:] 90 | pos = string.find(token) 91 | parts.append(string) 92 | return parts 93 | 94 | def tokenize_string(self, string, tokens=None): 95 | if string is None: 96 | return None 97 | tokens = self.tokens if tokens is None else tokens 98 | parent = Block(None, None) 99 | parent.append(self._new_string_element(string)) 100 | for p in self.pairs: 101 | self._pair_tree(p, parent) 102 | for t in tokens: 103 | self._token_tree(t, parent) 104 | return parent 105 | 106 | def _pair_tree(self, p, node): 107 | if isinstance(node, Block): 108 | for c in node: 109 | self._pair_tree(p, c) 110 | return 111 | string = node.defaultValue 112 | index1 = string.find(p[0]) 113 | if index1 is -1: 114 | return 115 | index2 = string[index1:].find(p[1]) 116 | if index2 is -1: 117 | return 118 | index2 += index1 119 | block = Block(None, None) 120 | pre = string[:index1] 121 | token_start = string[index1] 122 | middle = string[index1 + 1:index2] 123 | token_end = string[index2] 124 | after = string[index2 + 1:] 125 | if len(pre): 126 | block.append(self._new_string_element(pre)) 127 | block.append(self._new_string_element(token_start)) 128 | block.append(self._new_string_element(middle)) 129 | block.append(self._new_string_element(token_end)) 130 | if len(after): 131 | block.append(self._new_string_element(after)) 132 | block.name = node.name 133 | node.parent[node.name] = block 134 | 135 | def _token_tree(self, token, node): 136 | if isinstance(node, Block): 137 | for c in node: 138 | self._token_tree(token, c) 139 | return 140 | if len(node.defaultValue) < 2: 141 | return 142 | stuff = self._split(node.defaultValue, token) 143 | if not len(stuff): 144 | return 145 | block = Block(node.name, None) 146 | for s in stuff: 147 | block.append(self._new_string_element(s)) 148 | node.parent[node.name] = block 149 | -------------------------------------------------------------------------------- /Peach/Fixups/strings.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import random 5 | import string 6 | import re 7 | import time 8 | 9 | from Peach.fixup import Fixup 10 | 11 | 12 | class Uppercase(Fixup): 13 | 14 | def __init__(self, ref): 15 | Fixup.__init__(self) 16 | self.ref = ref 17 | 18 | def fixup(self): 19 | stuff = self.context.findDataElementByName(self.ref).getValue() 20 | if stuff is None: 21 | raise Exception("Error: Uppercase was unable to locate " 22 | "[{}].".format(self.ref)) 23 | return stuff.upper() 24 | 25 | 26 | class Escape(Fixup): 27 | 28 | def __init__(self, ref): 29 | Fixup.__init__(self) 30 | self.ref = ref 31 | 32 | def fixup(self): 33 | stuff = self.context.findDataElementByName(self.ref).getValue() 34 | if stuff is None: 35 | raise Exception("Error: Escape was unable to locate " 36 | "[{}].".format(self.ref)) 37 | return '"' + stuff + '"' 38 | 39 | 40 | class LogicalField(Fixup): 41 | 42 | def __init__(self, values, listsep="; ", unitsep=","): 43 | Fixup.__init__(self) 44 | self.values = values 45 | self.listsep = listsep 46 | self.unitsep = unitsep 47 | 48 | def fixup(self): 49 | values = string.split(self.values, self.listsep) 50 | if values is None: 51 | raise Exception("Error: LogicalField was unable to locate its " 52 | "values.") 53 | rndIndex = random.randint(0, len(values) - 1) 54 | rndValue = string.split(values[rndIndex], self.unitsep) 55 | if rndValue[0] == "String": 56 | return TranslateHexDigits(rndValue[1]) 57 | if rndValue[0] == "Number": 58 | return int(rndValue[1], 16) 59 | return TranslateHexDigits(rndValue[1]) 60 | 61 | 62 | class RandomField(Fixup): 63 | 64 | def __init__(self, minlen, maxlen, fieldtype): 65 | Fixup.__init__(self) 66 | self.minlen = minlen 67 | self.maxlen = maxlen 68 | self.fieldtype = fieldtype 69 | 70 | def fixup(self): 71 | minlen = self.minlen 72 | maxlen = self.maxlen 73 | if minlen is None: 74 | raise Exception("Error: RandomField was unable to locate minlen.") 75 | if maxlen is None: 76 | raise Exception("Error: RandomField was unable to locate maxlen.") 77 | if int(minlen) > int(maxlen): 78 | raise Exception("Error: minlen ({}) > maxlen ({})." 79 | .format(minlen, maxlen)) 80 | value = "" 81 | length = random.randint(int(minlen), int(maxlen)) 82 | if self.fieldtype == "String": 83 | for _ in range(length): 84 | value += random.choice(string.printable) 85 | elif self.fieldtype == "Number": 86 | for _ in range(length): 87 | value += random.choice(string.digits) 88 | else: 89 | for _ in range(length): 90 | value += random.choice(string.hexdigits) 91 | value = TranslateHexDigits(value) 92 | return value 93 | 94 | 95 | class Padding(Fixup): 96 | 97 | def __init__(self, ref): 98 | Fixup.__init__(self) 99 | self.ref = ref 100 | 101 | def fixup(self): 102 | ref = self.context.findDataElementByName(self.ref) 103 | stuff = ref.getValue() 104 | if stuff is None: 105 | raise Exception("Error: PaddingFixup was unable to locate " 106 | "[{}]".format(self.ref)) 107 | x = len(stuff) % 4 108 | return x * "\x00" if x == 0 else (4 - x) * "\x00" 109 | 110 | 111 | def TranslateHexDigits(value): 112 | regsHex = ( 113 | re.compile(r"^([,\s]*\\x([a-zA-Z0-9]{2})[,\s]*)"), 114 | re.compile(r"^([,\s]*%([a-zA-Z0-9]{2})[,\s]*)"), 115 | re.compile(r"^([,\s]*0x([a-zA-Z0-9]{2})[,\s]*)"), 116 | re.compile(r"^([,\s]*x([a-zA-Z0-9]{2})[,\s]*)"), 117 | re.compile(r"^([,\s]*([a-zA-Z0-9]{2})[,\s]*)") 118 | ) 119 | ret = "" 120 | valueLen = len(value) + 1 121 | while valueLen > len(value): 122 | valueLen = len(value) 123 | for i in range(len(regsHex)): 124 | match = regsHex[i].search(value) 125 | if match is not None: 126 | while match is not None: 127 | ret += chr(int(match.group(2), 16)) 128 | value = regsHex[i].sub('', value) 129 | match = regsHex[i].search(value) 130 | break 131 | return ret 132 | 133 | 134 | class Timestamp(Fixup): 135 | 136 | def __init__(self, ref): 137 | Fixup.__init__(self) 138 | self.ref = ref 139 | 140 | def fixup(self): 141 | ref = self.context.findDataElementByName(self.ref) 142 | stuff = ref.getValue() 143 | if stuff is None: 144 | raise Exception("Error: Timestamp Fixup was unable to locate " 145 | "[{}]" % self.ref) 146 | values = time.strftime("%y %m %d %H %M %S %z", 147 | time.gmtime()).split(" ") 148 | x = 0 149 | if values[-1][0] == "+": 150 | x = 0 151 | if values[-1][0] == "-": 152 | x = 1 153 | values[-1] = "%02d" % (int(values[-1][1:3]) | x << 3) 154 | s = "" 155 | for i in values: 156 | s += i[::-1] 157 | s = s.decode("hex") 158 | return s 159 | -------------------------------------------------------------------------------- /Peach/Transformers/Cryptography/UnixMD5Crypt.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import string 5 | import hashlib 6 | 7 | from Peach.transformer import Transformer 8 | 9 | 10 | MAGIC = '$1$' 11 | ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 12 | 13 | 14 | def to64(v, n): 15 | ret = '' 16 | while n - 1 >= 0: 17 | n -= 1 18 | ret += ITOA64[v & 0x3f] 19 | v >>= 6 20 | return ret 21 | 22 | 23 | def apache_md5_crypt(pw, salt): 24 | # change the Magic string to match the one used by Apache 25 | return unix_md5_crypt(pw, salt, '$apr1$') 26 | 27 | 28 | def unix_md5_crypt(pw, salt, magic=None): 29 | if magic is None: 30 | magic = MAGIC 31 | 32 | # Take care of the magic string if present 33 | if salt[:len(magic)] == magic: 34 | salt = salt[len(magic):] 35 | 36 | # salt can have up to 8 characters: 37 | salt = string.split(salt, '$', 1)[0] 38 | salt = salt[:8] 39 | 40 | ctx = pw + magic + salt 41 | final = hashlib.md5(pw + salt + pw).digest() 42 | 43 | for pl in range(len(pw), 0, -16): 44 | if pl > 16: 45 | ctx = ctx + final[:16] 46 | else: 47 | ctx = ctx + final[:pl] 48 | 49 | # Now the 'weird' xform (??) 50 | i = len(pw) 51 | while i: 52 | if i & 1: 53 | ctx += chr(0) 54 | else: 55 | ctx = ctx + pw[0] 56 | i >>= 1 57 | 58 | final = hashlib.md5(ctx).digest() 59 | 60 | # The following is supposed to make things run slower. 61 | for i in range(1000): 62 | ctx1 = '' 63 | if i & 1: 64 | ctx1 = ctx1 + pw 65 | else: 66 | ctx1 = ctx1 + final[:16] 67 | if i % 3: 68 | ctx1 = ctx1 + salt 69 | if i % 7: 70 | ctx1 = ctx1 + pw 71 | if i & 1: 72 | ctx1 = ctx1 + final[:16] 73 | else: 74 | ctx1 = ctx1 + pw 75 | final = hashlib.md5(ctx1).digest() 76 | 77 | # Final xform 78 | passwd = '' 79 | passwd += to64((int(ord(final[0])) << 16) 80 | | (int(ord(final[6])) << 8) 81 | | (int(ord(final[12]))), 4) 82 | passwd += to64((int(ord(final[1])) << 16) 83 | | (int(ord(final[7])) << 8) 84 | | (int(ord(final[13]))), 4) 85 | passwd += to64((int(ord(final[2])) << 16) 86 | | (int(ord(final[8])) << 8) 87 | | (int(ord(final[14]))), 4) 88 | passwd += to64((int(ord(final[3])) << 16) 89 | | (int(ord(final[9])) << 8) 90 | | (int(ord(final[15]))), 4) 91 | passwd += to64((int(ord(final[4])) << 16) 92 | | (int(ord(final[10])) << 8) 93 | | (int(ord(final[5]))), 4) 94 | passwd += to64((int(ord(final[11]))), 2) 95 | return magic + salt + '$' + passwd 96 | 97 | 98 | class ApacheMd5Crypt(Transformer): 99 | """Apache style MD5 crypt. 100 | If no salt is specified will use first two chars of data, ala pwd style. 101 | 102 | Uses '$apr1$' as magic. 103 | 104 | From underlying docs: 105 | I{apache_md5_crypt() provides a function compatible with Apache's 106 | .htpasswd files. This was contributed by Bryan Hart .} 107 | 108 | I{"THE BEER-WARE LICENSE" (Revision 42): 109 | wrote this file. As long as you retain this notice 110 | you can do whatever you want with this stuff. If we meet some day, and you 111 | think this stuff is worth it, you can buy me a beer in return. 112 | Poul-Henning Kamp} 113 | """ 114 | 115 | _salt = None 116 | 117 | def __init__(self, salt=None): 118 | """ 119 | :param salt: salt for crypt (optional) 120 | :type salt: str 121 | """ 122 | Transformer.__init__(self) 123 | self._salt = salt 124 | 125 | def realEncode(self, data): 126 | if self._salt is None: 127 | return apache_md5_crypt(data, data[:2]) 128 | return apache_md5_crypt(data, self._salt) 129 | 130 | 131 | class UnixMd5Crypt(Transformer): 132 | """UNIX style MD5 crypt. 133 | If no salt is specified will use first two chars of data, ala pwd style. 134 | 135 | From underlying docs: 136 | 137 | I{unix_md5_crypt() provides a crypt()-compatible interface to the 138 | rather new MD5-based crypt() function found in modern operating systems. 139 | It's based on the implementation found on FreeBSD 2.2.[56]-RELEASE and 140 | contains the following license in it:} 141 | 142 | I{"THE BEER-WARE LICENSE" (Revision 42): 143 | wrote this file. As long as you retain this notice 144 | you can do whatever you want with this stuff. If we meet some day, and you 145 | think this stuff is worth it, you can buy me a beer in return. 146 | Poul-Henning Kamp} 147 | """ 148 | 149 | _salt = None 150 | _magic = None 151 | 152 | def __init__(self, salt=None, magic=None): 153 | """ 154 | :type salt: str 155 | :param salt: salt for crypt (optional) 156 | :type magic: str 157 | :param magic: magic, usually $1$ on unix (optional) 158 | """ 159 | Transformer.__init__(self) 160 | self._salt = salt 161 | self._magic = magic 162 | 163 | def realEncode(self, data): 164 | if self._salt is None: 165 | return unix_md5_crypt(data, data[:2], self._magic) 166 | return unix_md5_crypt(data, self._salt, self._magic) 167 | -------------------------------------------------------------------------------- /Peach/mutatestrategies.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Peach.mutator import MutatorCompleted 5 | from Peach.Utilities.common import setAttributesFromParams 6 | 7 | 8 | class MutationStrategy(object): 9 | """ 10 | Mutation strategies control how fuzzing occurs, weather that be changing 11 | each field sequentially or randomly selecting 5 fields to change each test 12 | case. 13 | Mutation strategies are implemented by overriding event handlers and using 14 | them to affect the state machine, data models, etc. 15 | """ 16 | 17 | DefaultStrategy = None 18 | 19 | def __init__(self, node, parent): 20 | self.parent = parent # This will be a object 21 | setAttributesFromParams(node) 22 | 23 | def isFinite(self): 24 | """ 25 | Will this mutation strategy ever end? 26 | """ 27 | return True 28 | 29 | def getCount(self): 30 | """ 31 | Return the number of test cases. 32 | """ 33 | return None 34 | 35 | def next(self): 36 | """ 37 | Go to next test sequence. Throws MutatorCompleted when we are done. 38 | """ 39 | raise MutatorCompleted() 40 | 41 | def currentMutator(self): 42 | """ 43 | Return the current Mutator in use. 44 | """ 45 | pass 46 | 47 | def onTestCaseStarting(self, test, count, stateEngine): 48 | """ 49 | Called as we start a test case. 50 | 51 | :param test: current Test being run 52 | :type test: Test 53 | :param count: current test number 54 | :type count: int 55 | :param stateEngine: StateEngine instance in use 56 | :type stateEngine: StateEngine 57 | """ 58 | pass 59 | 60 | def onTestCaseFinished(self, test, count, stateEngine): 61 | """ 62 | Called as we exit a test case 63 | 64 | :param test: current Test being run 65 | :type test: Test 66 | :param count: current test number 67 | :type count: int 68 | :param stateEngine: StateEngine instance in use 69 | :type stateEngine: StateEngine 70 | """ 71 | pass 72 | 73 | def onFaultDetected(self, test, count, stateEngine, results, actionValues): 74 | """ 75 | Called if a fault was detected during our current test case. 76 | 77 | :param test: current Test being run 78 | :type test: Test 79 | :param count: current test number 80 | :type count: int 81 | :param stateEngine: StateEngine instance in use 82 | :type stateEngine: StateEngine 83 | :param results: monitor results 84 | :type results: dict 85 | :param actionValues: values used to perform test 86 | :type actionValues: dict 87 | """ 88 | pass 89 | 90 | def onStateMachineStarting(self, stateEngine): 91 | """ 92 | Called as we enter the state machine. 93 | 94 | :param stateEngine: StateEngine instance in use 95 | :type stateEngine: StateEngine 96 | """ 97 | pass 98 | 99 | def onStateMachineFinished(self, stateEngine): 100 | """ 101 | Called as we exit the state machine. 102 | 103 | :param stateEngine: StateEngine instance in use 104 | :type stateEngine: StateEngine 105 | """ 106 | pass 107 | 108 | def onStateStarting(self, stateEngine, state): 109 | """ 110 | Called as we enter a new state. 111 | 112 | :param stateEngine: StateEngine instance in use 113 | :type stateEngine: StateEngine 114 | :param state: State 115 | :type state: current state 116 | """ 117 | pass 118 | 119 | def onStateFinished(self, stateEngine, state): 120 | """ 121 | Called as we exit a state. 122 | 123 | :param stateEngine: StateEngine instance in use 124 | :type stateEngine: StateEngine 125 | :param state: State 126 | :type state: current state 127 | """ 128 | pass 129 | 130 | def onStateChange(self, currentState, newState): 131 | """ 132 | Called before state is changed. If result if non-None we can select 133 | a different state to change to. 134 | 135 | :param currentState: current state 136 | :type currentState: State 137 | :param newState: new state we are moving to 138 | :type newState: State 139 | :returns: None or a different state instance 140 | :rtype: State 141 | """ 142 | return None 143 | 144 | def onActionStarting(self, state, action): 145 | """ 146 | Called as we enter an action. 147 | 148 | :param state: current state 149 | :type state: State 150 | :param action: action we are starting 151 | :type action: Action 152 | """ 153 | pass 154 | 155 | def onActionFinished(self, state, action): 156 | """ 157 | Called as we exit an action. 158 | 159 | :param state: current state 160 | :type state: State 161 | :param action: action we are starting 162 | :type action: Action 163 | """ 164 | pass 165 | 166 | def onDataModelGetValue(self, action, dataModel): 167 | """ 168 | Called before getting a value from a data model. 169 | 170 | :param action: action we are starting 171 | :type action: Action 172 | :param dataModel: data model we are using 173 | :type dataModel: DataModel 174 | """ 175 | pass 176 | -------------------------------------------------------------------------------- /Peach/Analyzers/asn1.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import os 5 | import sys 6 | 7 | from Peach.analyzer import * 8 | from Peach.Engine.dom import * 9 | from Peach.Engine.common import * 10 | 11 | try: 12 | from pyasn1.type import univ 13 | import pyasn1.codec.ber.decoder 14 | import pyasn1.codec.cer.decoder 15 | import pyasn1.codec.der.decoder 16 | 17 | import pyasn1.codec.ber.encoder 18 | import pyasn1.codec.cer.encoder 19 | import pyasn1.codec.der.encoder 20 | 21 | except: 22 | #raise PeachException("Error loading pyasn1 library. This library\ncan be installed from the dependencies folder.\n\n") 23 | pass 24 | 25 | 26 | class Asn1Analyzer(Analyzer): 27 | """ 28 | Produces data models or peach pits from XML documents. 29 | """ 30 | 31 | #: Does analyzer support asDataElement() 32 | supportDataElement = True 33 | #: Does analyzer support asCommandLine() 34 | supportCommandLine = False 35 | #: Does analyzer support asTopLevel() 36 | supportTopLevel = True 37 | 38 | def __init__(self): 39 | pass 40 | 41 | def analyzeAsn1(self, codec, data): 42 | 43 | decoder = eval("pyasn1.codec.%s.decoder" % codec) 44 | 45 | asn1Obj = decoder.decode(data)[0] 46 | return self.Asn12Peach(codec, asn1Obj) 47 | 48 | def Asn12Peach(self, codec, asn1Obj): 49 | 50 | obj = Asn1Type(None, None) 51 | obj.asn1Type = asn1Obj.__class__.__name__ 52 | obj.encodeType = codec 53 | obj.asnTagSet = None #asn1Obj._tagSet 54 | obj.asn1Spec = None # asn1Obj._asn1Spec 55 | 56 | if hasattr(asn1Obj, "_value"): 57 | value = asn1Obj._value 58 | obj.objType = type(value) 59 | 60 | if type(value) == long or type(value) == int: 61 | n = Number(None, None) 62 | n.defaultValue = str(value) 63 | n.size = 32 64 | 65 | obj.append(n) 66 | 67 | elif type(value) == str: 68 | # Could be blob or string...hmmm 69 | 70 | # Sometimes we have ASN.1 inside of ASN.1 71 | # most common for OctetString type 72 | if asn1Obj.__class__.__name__ == 'OctetString': 73 | try: 74 | decoder = eval("pyasn1.codec.%s.decoder" % codec) 75 | subAsn1 = decoder.decode(asn1Obj._value)[0] 76 | 77 | child = self.Asn12Peach(codec, subAsn1) 78 | b = Block(None, None) 79 | b.append(child) 80 | 81 | except: 82 | b = Blob(None, None) 83 | b.defaultValue = value 84 | 85 | else: 86 | b = Blob(None, None) 87 | b.defaultValue = value 88 | 89 | obj.append(b) 90 | 91 | elif type(value) == tuple: 92 | # Probably and ObjectIdentifier! 93 | 94 | if asn1Obj.__class__.__name__ == 'ObjectIdentifier': 95 | oid = [] 96 | for i in value: 97 | oid.append(str(i)) 98 | 99 | b = String(None, None) 100 | b.defaultValue = ".".join(oid) 101 | 102 | obj.append(b) 103 | 104 | elif asn1Obj.__class__.__name__ == 'BitString': 105 | # Make this a blob 106 | b = Blob(None, None) 107 | 108 | encoder = eval("pyasn1.codec.%s.encoder" % codec) 109 | b.defaultValue = encoder.encode(asn1Obj)[4:] 110 | 111 | obj.append(b) 112 | 113 | else: 114 | print("UNKNOWN TUPLE TYPE") 115 | print(asn1Obj.__class__.__name__) 116 | print(value) 117 | raise Exception("foo") 118 | 119 | if hasattr(asn1Obj, "_componentValues"): 120 | for c in asn1Obj._componentValues: 121 | child = self.Asn12Peach(codec, c) 122 | obj.append(child) 123 | 124 | return obj 125 | 126 | def asDataElement(self, parent, args, dataBuffer): 127 | """ 128 | Called when Analyzer is used in a data model. 129 | 130 | Should return a DataElement such as Block, Number or String. 131 | """ 132 | 133 | dom = self.analyzeAsn1("der", dataBuffer) 134 | 135 | # Replace parent with new dom 136 | 137 | dom.name = parent.name 138 | parentOfParent = parent.parent 139 | 140 | indx = parentOfParent.index(parent) 141 | del parentOfParent[parent.name] 142 | parentOfParent.insert(indx, dom) 143 | 144 | # now just cross our fingers :) 145 | 146 | def asCommandLine(self, args): 147 | """ 148 | Called when Analyzer is used from command line. Analyzer 149 | should produce Peach PIT XML as output. 150 | """ 151 | 152 | raise Exception("asCommandLine not supported (yet)") 153 | 154 | #try: 155 | # inFile = args["xmlfile"] 156 | # outFile = args["out"] 157 | #except: 158 | # raise PeachException("XmlAnalyzer requires two parameters, xmlfile and out.") 159 | # 160 | #xml = _Xml2Peach().xml2Peach("file:"+inFile) 161 | # 162 | #fd = open(outfile, "wb+") 163 | #fd.write(xml) 164 | #fd.close() 165 | 166 | def asTopLevel(self, peach, args): 167 | """ 168 | Called when Analyzer is used from top level. 169 | 170 | From the top level producing zero or more data models and 171 | state models is possible. 172 | """ 173 | raise Exception("asTopLevel not supported") 174 | -------------------------------------------------------------------------------- /Peach/Agent/socketmon.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import sys 5 | import time 6 | import socket 7 | 8 | sys.path.append("..") 9 | sys.path.append("../..") 10 | 11 | from Peach.agent import Monitor 12 | from twisted.internet import reactor, protocol 13 | from threading import Thread 14 | 15 | g_socketData = None 16 | g_faultDetected = False 17 | g_stopReactor = False 18 | 19 | # TODO: Port this over to use ThreadedSelectReactor 20 | # http://twistedmatrix.com/documents/current/api/twisted.internet._threadedselect.ThreadedSelectReactor.html 21 | 22 | class SocketMonitor(Monitor): 23 | 24 | def __init__(self, args): 25 | """ 26 | Constructor. Arguments are supplied via the Peach XML file. 27 | 28 | @type args: Dictionary 29 | @param args: Dictionary of parameters 30 | """ 31 | try: 32 | self._name = "Socket Monitor" 33 | self._ip = "" 34 | self._port = "" 35 | self._protocol = "" 36 | self._thread = None 37 | self._internalError = False 38 | self._stopOnFault = False 39 | # Report an error if no MemoryLimit and/or neither pid nor 40 | # processName is defined. 41 | while 1: 42 | if args.has_key('IP'): 43 | self._ip = str(args["IP"]).replace("'''", "") 44 | else: 45 | print("Socket Monitor: No IP specified, using using " 46 | "127.0.0.1") 47 | self._ip = "127.0.0.1" 48 | if args.has_key('Port'): 49 | self._port = int(args['Port'].replace("'''", "")) 50 | print("Socket Monitor: Listening on Port: {}" 51 | .format(self._port)) 52 | else: 53 | print("Socket Monitor: No Port specified, using 80") 54 | self._port = "8002" 55 | if args.has_key('Protocol'): 56 | self._protocol = args['Protocol'].replace("'''", "") 57 | print("Socket Monitor: Protocol = %s" % self._protocol) 58 | else: 59 | print("Socket Monitor: No Protocol specified, using TCP") 60 | self._protocol = "tcp" 61 | break 62 | except: 63 | self._internalError = True 64 | print("Socket Monitor: Caught Exception Parsing Arguments") 65 | raise 66 | 67 | def OnTestStarting(self): 68 | """ 69 | Called right before start of test case or variation. 70 | """ 71 | global g_faultDetected 72 | global g_socketData 73 | # fire up twisted if it hasn't been already 74 | if self._thread is None: 75 | self._thread = ReactorThread(self._ip, self._port, self._protocol) 76 | self._thread.start() 77 | g_socketData = None 78 | g_faultDetected = False 79 | return 80 | 81 | def GetMonitorData(self): 82 | """ 83 | Get any monitored data from a test case. 84 | """ 85 | global g_socketData 86 | return {'SocketData.txt': str(g_socketData)} 87 | 88 | def DetectedFault(self): 89 | """ 90 | Check if a fault was detected. 91 | """ 92 | global g_faultDetected 93 | return g_faultDetected 94 | 95 | def OnShutdown(self): 96 | """ 97 | Called when Agent is shutting down, typically at end of a test run or 98 | when a Stop-Run occurs. 99 | """ 100 | global g_stopReactor 101 | g_stopReactor = True 102 | s = None 103 | # hack to stop reactor 104 | if self._protocol.lower() == "tcp": 105 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 106 | else: 107 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 108 | s.connect((self._ip, int(self._port))) 109 | s.close() 110 | 111 | def StopRun(self): 112 | """ 113 | Return True to force test run to fail. This should return True if an 114 | unrecoverable error occurs. 115 | """ 116 | return self._internalError 117 | 118 | 119 | class ReactorThread(Thread): 120 | def __init__(self, ip, port, protocol): 121 | Thread.__init__(self) 122 | self._ip = ip 123 | self._port = port 124 | self._protocol = protocol 125 | self._factory = None 126 | 127 | def run(self): 128 | self._factory = protocol.ServerFactory() 129 | self._factory.protocol = Listener 130 | if self._protocol.lower() == "tcp": 131 | reactor.listenTCP(int(self._port), self._factory) 132 | else: 133 | reactor.listenUDP(int(self._port), self._factory) 134 | reactor.run(installSignalHandlers=0) 135 | 136 | 137 | class Listener(protocol.Protocol): 138 | def connectionMade(self): 139 | global g_stopReactor 140 | global g_socketData 141 | # hack until ThreadedSelectReactor is implemented 142 | if g_stopReactor: 143 | print("Socket Monitor: Stopping Reactor") 144 | reactor.stop() 145 | else: 146 | host = self.transport.getHost() 147 | g_socketData = host.host + ":" + str(host.port) 148 | global g_faultDetected 149 | g_faultDetected = True 150 | 151 | 152 | if __name__ == "__main__": 153 | d = { 154 | "IP": "127.0.0.1", 155 | "Port": "8002", 156 | "Protocol": "tcp" 157 | } 158 | a = SocketMonitor(d) 159 | a.OnTestStarting() 160 | while True: 161 | a.OnTestStarting() 162 | time.sleep(2) 163 | print(a.DetectedFault()) 164 | a.OnTestFinished() 165 | a.OnShutdown() 166 | -------------------------------------------------------------------------------- /Peach/strategy.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | from Engine.common import * 5 | 6 | 7 | class StrategyError(PeachException): 8 | """Strategy Exception""" 9 | pass 10 | 11 | 12 | class RouteDescriptor(object): 13 | """Used to describe the route.""" 14 | 15 | def __init__(self): 16 | self.paths = [] 17 | self.index = 0 18 | 19 | def addPath(self, path): 20 | if path is None: 21 | raise PeachException("Argument 'path' cannot be None!") 22 | self.paths.append(path) 23 | 24 | def reset(self): 25 | self.index = 0 26 | 27 | def clear(self): 28 | del self.paths[:] 29 | 30 | def hasNextSingle(self): 31 | """ 32 | This method is handy when there is no pair but only a path. 33 | """ 34 | return len(self.paths) == 1 and self.index < len(self.paths) 35 | 36 | def nextSingle(self): 37 | """ 38 | To get the current path. 39 | """ 40 | if not self.hasNextSingle(): 41 | raise PeachException("End of the route description is reached!") 42 | self.index += 1 43 | return self.paths[self.index - 1] 44 | 45 | def hasNextPair(self): 46 | """ 47 | Returns True when it has two paths more to visit otherwise False. 48 | """ 49 | return self.index < len(self.paths) - 1 50 | 51 | def nextPair(self): 52 | """ 53 | Returns the next pair which strategy will find a proper path in 54 | between. If hasNextPair() returns False then this method completes 55 | the pair to be returned with a None and yields the result. 56 | """ 57 | if not self.hasNextPair(): 58 | raise PeachException("End of the route-pairs is reached!") 59 | self.index += 1 60 | return [self.paths[self.index - 1], self.paths[self.index]] 61 | 62 | 63 | class Strategy(object): 64 | """ 65 | Strategy is an abstract class that is used to define a method to find out 66 | a route between start and destination paths. 67 | """ 68 | 69 | def __init__(self, stateMachine, routeDescriptor, params=None): 70 | self.params = {} 71 | if params is not None: 72 | self.params = params 73 | if routeDescriptor is None: 74 | raise PeachException("Argument 'routeDescriptor' cannot be None!") 75 | self.routeDesc = routeDescriptor 76 | self.stateMachine = stateMachine 77 | 78 | def getRoute(self): 79 | """ 80 | This method invokes abstract _findRoute method for each pair taken 81 | from routeDescriptor to obtain a proper route and finally returns 82 | the route. 83 | """ 84 | route = [] 85 | while self.routeDesc.hasNextPair(): 86 | start, destination = self.routeDesc.nextPair() 87 | partialRoute = self._findRoute(start, destination) 88 | if len(route) == 0: 89 | route.extend(partialRoute) 90 | else: 91 | route.extend(partialRoute[1:]) 92 | if self.routeDesc.hasNextSingle(): 93 | lastPath = self.routeDesc.nextSingle() 94 | route.extend(self._findRoute(lastPath, None)) 95 | return route 96 | 97 | def _findRoute(self, start, destination): 98 | """ 99 | This method is used to explore a route(list of paths) in between 100 | start and destination paths. 101 | 102 | :param start: start path 103 | :param destination: destination path 104 | :returns: a list of paths starting with parameter start and ending 105 | with parameter destination. 106 | """ 107 | pass 108 | 109 | def _reset(self): 110 | """ 111 | Used to reset a strategy especially when re-discovering the route. 112 | """ 113 | self.routeDesc.reset() 114 | 115 | 116 | class StrategyCollection(Strategy): 117 | """ 118 | This class behaves like a proxy between strategies defined in XML file, 119 | which is used to run all the strategies respectively to produce a 120 | resultant route. 121 | """ 122 | 123 | def __init__(self): 124 | Strategy.__init__(self, None, RouteDescriptor()) 125 | self.strategies = [] 126 | self.route = None 127 | 128 | def addStrategy(self, strategy): 129 | self.strategies.append(strategy) 130 | # Re-explore the route as we have a new strategy. 131 | self._reset() 132 | 133 | def getRoute(self): 134 | if self.route is not None: 135 | return self.route 136 | self.route = [] 137 | for strategy in self.strategies: 138 | self.route.extend(strategy.getRoute()) 139 | return self.route 140 | 141 | def _reset(self): 142 | self.route = None 143 | for strategy in self.strategies: 144 | strategy._reset() 145 | 146 | 147 | class StrategyFactory(object): 148 | """ 149 | To centralize the creation of strategies. 150 | """ 151 | 152 | def __init__(self, defStrategy="default.StaticStrategy"): 153 | self.defStrategy = defStrategy 154 | 155 | def createStrategy(self, stateMachine, routeDescriptor, 156 | clazz=None, params=None): 157 | if clazz is None: 158 | clazz = self.defStrategy 159 | try: 160 | code = clazz + "(stateMachine, routeDescriptor, params)" 161 | print("StrategyFactory.createStrategy: {}".format(code)) 162 | strategy = eval(code) 163 | if strategy is None: 164 | raise PeachException("Unable to create Strategy [{}]" 165 | .format(clazz)) 166 | return strategy 167 | except: 168 | raise PeachException("Unable to create Strategy [{}]" 169 | .format(clazz)) 170 | -------------------------------------------------------------------------------- /Peach/Transformers/Compression/Zlib.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import sys 5 | import ctypes as C 6 | import os 7 | from ctypes import util 8 | 9 | from Peach.transformer import Transformer 10 | 11 | 12 | if sys.platform == "linux2" or sys.platform == "darwin": 13 | _zlib = C.cdll.LoadLibrary(util.find_library('libz')) 14 | elif sys.platform == "win32": 15 | _zlib = C.cdll.LoadLibrary(os.path.join(os.path.dirname(__file__), "zlib1.dll")) 16 | else: 17 | raise NotImplementedError 18 | 19 | 20 | class _z_stream(C.Structure): 21 | _fields_ = [ 22 | ("next_in", C.POINTER(C.c_ubyte)), 23 | ("avail_in", C.c_uint), 24 | ("total_in", C.c_ulong), 25 | ("next_out", C.POINTER(C.c_ubyte)), 26 | ("avail_out", C.c_uint), 27 | ("total_out", C.c_ulong), 28 | ("msg", C.c_char_p), 29 | ("state", C.c_void_p), 30 | ("zalloc", C.c_void_p), 31 | ("zfree", C.c_void_p), 32 | ("opaque", C.c_void_p), 33 | ("data_type", C.c_int), 34 | ("adler", C.c_ulong), 35 | ("reserved", C.c_ulong), 36 | ] 37 | 38 | 39 | ZLIB_VERSION = C.c_char_p("1.2.3") 40 | Z_NULL = 0x00 41 | Z_OK = 0x00 42 | Z_STREAM_END = 0x01 43 | Z_NEED_DICT = 0x02 44 | Z_BUF_ERR = -0x05 45 | Z_NO_FLUSH = 0x00 46 | Z_SYNC_FLUSH = 0x02 47 | Z_FINISH = 0x04 48 | CHUNK = 1024 * 128 49 | 50 | 51 | class Compressor(object): 52 | def __init__(self, level=-1, dictionary=None): 53 | self.level = level 54 | self.st = _z_stream() 55 | err = _zlib.deflateInit_(C.byref(self.st), self.level, ZLIB_VERSION, 56 | C.sizeof(self.st)) 57 | assert err == Z_OK, err 58 | if dictionary: 59 | err = _zlib.deflateSetDictionary( 60 | C.byref(self.st), 61 | C.cast(C.c_char_p(dictionary), C.POINTER(C.c_ubyte)), 62 | len(dictionary) 63 | ) 64 | assert err == Z_OK, err 65 | 66 | def __call__(self, input): 67 | outbuf = C.create_string_buffer(CHUNK) 68 | self.st.avail_in = len(input) 69 | self.st.next_in = C.cast(C.c_char_p(input), C.POINTER(C.c_ubyte)) 70 | self.st.next_out = C.cast(outbuf, C.POINTER(C.c_ubyte)) 71 | self.st.avail_out = CHUNK 72 | err = _zlib.deflate(C.byref(self.st), Z_SYNC_FLUSH) 73 | if err in [Z_OK, Z_STREAM_END]: 74 | x = outbuf[:CHUNK - self.st.avail_out] 75 | return x 76 | else: 77 | raise AssertionError(err) 78 | 79 | def __del__(self): 80 | err = _zlib.deflateEnd(C.byref(self.st)) 81 | 82 | 83 | class Decompressor(object): 84 | def __init__(self, dictionary=None): 85 | self.dictionary = dictionary 86 | self.st = _z_stream() 87 | err = _zlib.inflateInit2_(C.byref(self.st), 15, ZLIB_VERSION, 88 | C.sizeof(self.st)) 89 | assert err == Z_OK, err 90 | 91 | def __call__(self, input): 92 | outbuf = C.create_string_buffer(CHUNK) 93 | self.st.avail_in = len(input) 94 | self.st.next_in = C.cast(C.c_char_p(input), C.POINTER(C.c_ubyte)) 95 | self.st.avail_out = CHUNK 96 | self.st.next_out = C.cast(outbuf, C.POINTER(C.c_ubyte)) 97 | err = _zlib.inflate(C.byref(self.st), Z_SYNC_FLUSH) 98 | if err == Z_NEED_DICT: 99 | assert self.dictionary, "no dictionary provided" 100 | dict_id = _zlib.adler32(0, 101 | C.cast(C.c_char_p(self.dictionary), C.POINTER(C.c_ubyte)), 102 | len(self.dictionary)) 103 | err = _zlib.inflateSetDictionary( 104 | C.byref(self.st), 105 | C.cast(C.c_char_p(self.dictionary), C.POINTER(C.c_ubyte)), 106 | len(self.dictionary) 107 | ) 108 | assert err == Z_OK, err 109 | err = _zlib.inflate(C.byref(self.st), Z_SYNC_FLUSH) 110 | if err in [Z_OK, Z_STREAM_END]: 111 | return outbuf[:CHUNK - self.st.avail_out] 112 | else: 113 | raise AssertionError(err) 114 | 115 | def __del__(self): 116 | err = _zlib.inflateEnd(C.byref(self.st)) 117 | 118 | 119 | class DictZLib(Transformer): 120 | def __init__(self, level=-1, dicto=None): 121 | Transformer.__init__(self) 122 | self._level = level 123 | self._dicto = dicto 124 | 125 | def realEncode(self, data): 126 | _compress = Compressor(self._level, self._dicto) 127 | return _compress(data) 128 | 129 | def realDecode(self, data): 130 | _decompress = Decompressor(self._dicto) 131 | return _decompress(data) 132 | 133 | 134 | class SPDYZLib(DictZLib): 135 | def __init__(self): 136 | _dicto = \ 137 | "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encod"\ 138 | "ingaccept-languageauthorizationexpectfromhostif-modified-sinceif"\ 139 | "-matchif-none-matchif-rangeif-unmodifiedsincemax-forwardsproxy-a"\ 140 | "uthorizationrangerefererteuser-agent1001012002012022032042052063"\ 141 | "0030130230330430530630740040140240340440540640740840941041141241"\ 142 | "3414415416417500501502503504505accept-rangesageetaglocationproxy"\ 143 | "-authenticatepublicretry-afterservervarywarningwww-authenticatea"\ 144 | "llowcontent-basecontent-encodingcache-controlconnectiondatetrail"\ 145 | "ertransfer-encodingupgradeviawarningcontent-languagecontent-leng"\ 146 | "thcontent-locationcontent-md5content-rangecontent-typeetagexpire"\ 147 | "slast-modifiedset-cookieMondayTuesdayWednesdayThursdayFridaySatu"\ 148 | "rdaySundayJanFebMarAprMayJunJulAugSepOctNovDecchunkedtext/htmlim"\ 149 | "age/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/pl"\ 150 | "ainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1.1status"\ 151 | "versionurl\0" 152 | DictZLib.__init__(self, dicto=_dicto) 153 | 154 | 155 | if __name__ == "__main__": 156 | print(SPDYZLib().realDecode(DictZLib().realEncode('big ba\x45ng'))) 157 | --------------------------------------------------------------------------------