├── examples ├── crypt4 ├── ELF-NoSoftwareBreakpoints └── wyvern_c85f1be480808a9da350faaa6104a19b ├── prebuilt ├── obj-ia32 │ ├── inscount0.o │ └── inscount0.so └── obj-intel64 │ ├── inscount0.o │ └── inscount0.so ├── installPin.sh ├── README.md └── pinCTF.py /examples/crypt4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/PinCTF/HEAD/examples/crypt4 -------------------------------------------------------------------------------- /prebuilt/obj-ia32/inscount0.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/PinCTF/HEAD/prebuilt/obj-ia32/inscount0.o -------------------------------------------------------------------------------- /prebuilt/obj-ia32/inscount0.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/PinCTF/HEAD/prebuilt/obj-ia32/inscount0.so -------------------------------------------------------------------------------- /prebuilt/obj-intel64/inscount0.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/PinCTF/HEAD/prebuilt/obj-intel64/inscount0.o -------------------------------------------------------------------------------- /examples/ELF-NoSoftwareBreakpoints: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/PinCTF/HEAD/examples/ELF-NoSoftwareBreakpoints -------------------------------------------------------------------------------- /prebuilt/obj-intel64/inscount0.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/PinCTF/HEAD/prebuilt/obj-intel64/inscount0.so -------------------------------------------------------------------------------- /examples/wyvern_c85f1be480808a9da350faaa6104a19b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisTheCoolHut/PinCTF/HEAD/examples/wyvern_c85f1be480808a9da350faaa6104a19b -------------------------------------------------------------------------------- /installPin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORKDIR=$(pwd) 4 | 5 | #Pull latest from https://software.intel.com/en-us/articles/pin-a-binary-instrumentation-tool-downloads 6 | 7 | URL=https://software.intel.com/sites/landingpage/pintool/downloads/pin-3.11-97998-g7ecce2dac-gcc-linux.tar.gz 8 | 9 | wget $URL -O pin.tar.gz 10 | 11 | tar -xvf pin.tar.gz 12 | 13 | #Install Ubuntu Dependencies 14 | sudo apt-get install gcc-multilib g++-multilib libc6-dev-i386 15 | 16 | #Rename pin directory 17 | mv pin-* pin 18 | 19 | cd pin/source/tools/ManualExamples/ 20 | 21 | #Build for both 32 and 64 bit 22 | make inscount0.test TARGET=ia32 23 | make inscount0.test 24 | 25 | #Move inscount.so libraries up to PinCTF's directory 26 | mv obj-ia32 $WORKDIR/ 27 | mv obj-intel64 $WORKDIR/ 28 | 29 | #IPython used for debugging scripts 30 | sudo apt install python3-pip 31 | pip3 install ipython 32 | 33 | #Setup config with defaults 34 | printf "pin:%s 35 | library:%s 36 | count:%s 37 | seed:%s" $WORKDIR/pin $WORKDIR/obj-ia32 20 ABCD > config 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PinCTF 2 | 3 | This tool is designed to use [Intel's Pin Tool](https://software.intel.com/en-us/articles/pin-a-dynamic-binary-instrumentation-tool) to instrument reverse engineering binaries and count instructions. 4 | 5 | This tool is designed to use instruction counting as an avenue for [Side Channel Analysis](https://en.wikipedia.org/wiki/Side-channel_attack). By counting the number of instruction exeuted in a given reverse engineering program we can guess (Sometimes) that the more instructions that are executed per input, the closer we are to the flag. 6 | 7 | [![asciicast](https://asciinema.org/a/2bHy0y9MrGNa8Xp9MSPT17HTS.png)](https://asciinema.org/a/2bHy0y9MrGNa8Xp9MSPT17HTS) 8 | 9 | ## Install Pin 10 | Included in this repo is a script for pulling down Intel's PIN and instructions for building it on Ubuntu 16.04. 11 | 12 | ``` 13 | #This script will pull PIN and install dependencies needed. 14 | ./installPin.sh 15 | ``` 16 | 17 | ## Running PinCTF 18 | PinCTF is implemented as a python script wrapping PIN. It will execute a pin command then read from PIN's produced *inscount.out* file 19 | 20 | ``` 21 | [chris@Thor pinCTF]$ ./pinCTF.py -h 22 | usage: pinCTF.py [-h] [-f FILE] [-a] [-al] [-i] [-il] [-p PINLOCATION] 23 | [-l PINLIBRARYLOCATION] [-c COUNT] [-s SEED] [-r RANGE] 24 | [-sl SEEDLENGTH] [-st SEEDSTART] [-t] [-tc THREADCOUNT] 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | -f FILE, --file FILE file to run pin against 29 | -a, --arg Trace instructions for passed in argument 30 | -al, --argLength Trace instructions for passed in argument length 31 | -i, --input Trace instructions for given input 32 | -il, --inputLength Trace instructions for input length 33 | -p PINLOCATION, --pinLocation PINLOCATION 34 | Location of pin's directory 35 | -l PINLIBRARYLOCATION, --pinLibraryLocation PINLIBRARYLOCATION 36 | Location of pin's instruction0.so libraries 37 | -c COUNT, --count COUNT 38 | MaxLength to for length based pin 39 | -s SEED, --seed SEED Initial seed for input or arg pin 40 | -r RANGE, --range RANGE 41 | range of characters to iterate pin over 42 | -sl SEEDLENGTH, --seedLength SEEDLENGTH 43 | Initial seed length for input or arg pin 44 | -st SEEDSTART, --seedStart SEEDSTART 45 | Initial seed index for pin 46 | -t, --threading Enables threading 47 | -tc THREADCOUNT, --threadCount THREADCOUNT 48 | Number of threads 49 | ``` 50 | 51 | To compare instruction counts to length use the -il or -al commands 52 | The -c command is used to specifyhow many A's to test 53 | 54 | ``` 55 | ./pinCTF.py -f examples/wyvern_c85f1be480808a9da350faaa6104a19b -il -l obj-intel64/ -c 30 56 | 57 | Num : Instr Count AAAAAAAAAAAAAAAAAAA 58 | 1 : 2119788 59 | 2 : 2119789 60 | 3 : 2119789 61 | 4 : 2119784 62 | 5 : 2119788 63 | 6 : 2119789 64 | 7 : 2119791 65 | 8 : 2119782 66 | 9 : 2119786 67 | 10 : 2119787 68 | 11 : 2119791 69 | 12 : 2119786 70 | 13 : 2119790 71 | 14 : 2119791 72 | 15 : 2119818 73 | 16 : 2119822 74 | 17 : 2119826 75 | 18 : 2119825 76 | 19 : 2119831 77 | 20 : 2119824 78 | 21 : 2119830 79 | 22 : 2119831 80 | 23 : 2119835 81 | 24 : 2119826 82 | 25 : 2119830 83 | 26 : 2119831 84 | 27 : 2119835 85 | 28 : 2132982 86 | 29 : 2119834 87 | 30 : 2119863 88 | [+] Found Num 28 : Count 2132982 89 | 90 | ``` 91 | Now we know we that the flag is 28 characters long and we can start looking for a flag of 28 characters. 92 | 93 | 94 | Once you've found a length that seems to work you can use pin to change each value testing for instruction changes 95 | The -sl flag can be used to determine the length of the initial seed, and the -r flag can be used to choose what range to iterate over 96 | ``` 97 | ./pinCTF.py -f examples/wyvern_c85f1be480808a9da350faaa6104a19b -i -l obj-intel64/ -sl 28 -r abcdefghijklmnopqrstuvwxyz012345_-+LVMA -sk 98 | [+] iter 0 using d for dAAAAAAAAAAAAAAAAAAAAAAAAAAA 99 | [+] iter 1 using r for drAAAAAAAAAAAAAAAAAAAAAAAAAA 100 | [+] iter 2 using 4 for dr4AAAAAAAAAAAAAAAAAAAAAAAAA 101 | [+] iter 3 using g for dr4gAAAAAAAAAAAAAAAAAAAAAAAA 102 | [+] iter 4 using 0 for dr4g0AAAAAAAAAAAAAAAAAAAAAAA 103 | [+] iter 5 using n for dr4g0nAAAAAAAAAAAAAAAAAAAAAA 104 | [+] iter 6 using _ for dr4g0n_AAAAAAAAAAAAAAAAAAAAA 105 | [+] iter 7 using o for dr4g0n_oAAAAAAAAAAAAAAAAAAAA 106 | [+] iter 8 using r for dr4g0n_orAAAAAAAAAAAAAAAAAAA 107 | [+] iter 9 using _ for dr4g0n_or_AAAAAAAAAAAAAAAAAA 108 | [+] iter 10 using p for dr4g0n_or_pAAAAAAAAAAAAAAAAA 109 | [+] iter 11 using 4 for dr4g0n_or_p4AAAAAAAAAAAAAAAA 110 | [+] iter 12 using t for dr4g0n_or_p4tAAAAAAAAAAAAAAA 111 | [+] iter 13 using r for dr4g0n_or_p4trAAAAAAAAAAAAAA 112 | [+] iter 14 using i for dr4g0n_or_p4triAAAAAAAAAAAAA 113 | [+] iter 15 using c for dr4g0n_or_p4tricAAAAAAAAAAAA 114 | [+] iter 16 using 1 for dr4g0n_or_p4tric1AAAAAAAAAAA 115 | [+] iter 17 using a for dr4g0n_or_p4tric1aAAAAAAAAAA 116 | [+] iter 18 using n for dr4g0n_or_p4tric1anAAAAAAAAA 117 | [+] iter 19 using _ for dr4g0n_or_p4tric1an_AAAAAAAA 118 | [+] iter 20 using i for dr4g0n_or_p4tric1an_iAAAAAAA 119 | [+] iter 21 using t for dr4g0n_or_p4tric1an_itAAAAAA 120 | [+] iter 22 using 5 for dr4g0n_or_p4tric1an_it5AAAAA 121 | [+] iter 23 using _ for dr4g0n_or_p4tric1an_it5_AAAA 122 | [+] iter 24 using L for dr4g0n_or_p4tric1an_it5_LAAA 123 | [+] iter 25 using L for dr4g0n_or_p4tric1an_it5_LLAA 124 | [+] iter 26 using V for dr4g0n_or_p4tric1an_it5_LLVA 125 | [+] iter 27 using M for dr4g0n_or_p4tric1an_it5_LLVM 126 | [+] Found pattern dr4g0n_or_p4tric1an_it5_LLVM 127 | ``` 128 | 129 | ## Script tricks for PIN 130 | 131 | This process is pretty slow and can be sped up with threading. The -t (--threading) flag will enable threading and -tc represents the thread count 132 | 133 | ``` 134 | time ./pinCTF.py -f $(pwd)/examples/crypt4 -a -sl 26 --threading -tc 4 135 | [+] iter 0 using d for dAAAAAAAAAAAAAAAAAAAAAAAAA 136 | [+] iter 1 using y for dyAAAAAAAAAAAAAAAAAAAAAAAA 137 | [+] iter 2 using n for dynAAAAAAAAAAAAAAAAAAAAAAA 138 | [+] iter 3 using 4 for dyn4AAAAAAAAAAAAAAAAAAAAAA 139 | [+] iter 4 using m for dyn4mAAAAAAAAAAAAAAAAAAAAA 140 | [+] iter 5 using 1 for dyn4m1AAAAAAAAAAAAAAAAAAAA 141 | [+] iter 6 using c for dyn4m1cAAAAAAAAAAAAAAAAAAA 142 | [+] iter 7 using a for dyn4m1caAAAAAAAAAAAAAAAAAA 143 | [+] iter 8 using l for dyn4m1calAAAAAAAAAAAAAAAAA 144 | [+] iter 9 using l for dyn4m1callAAAAAAAAAAAAAAAA 145 | [+] iter 10 using y for dyn4m1callyAAAAAAAAAAAAAAA 146 | [+] iter 11 using _ for dyn4m1cally_AAAAAAAAAAAAAA 147 | [+] iter 12 using d for dyn4m1cally_dAAAAAAAAAAAAA 148 | [+] iter 13 using 3 for dyn4m1cally_d3AAAAAAAAAAAA 149 | [+] iter 14 using c for dyn4m1cally_d3cAAAAAAAAAAA 150 | [+] iter 15 using r for dyn4m1cally_d3crAAAAAAAAAA 151 | [+] iter 16 using y for dyn4m1cally_d3cryAAAAAAAAA 152 | [+] iter 17 using p for dyn4m1cally_d3crypAAAAAAAA 153 | [+] iter 18 using t for dyn4m1cally_d3cryptAAAAAAA 154 | [+] iter 19 using 3 for dyn4m1cally_d3crypt3AAAAAA 155 | [+] iter 20 using d for dyn4m1cally_d3crypt3dAAAAA 156 | [+] iter 21 using _ for dyn4m1cally_d3crypt3d_AAAA 157 | [+] iter 22 using c for dyn4m1cally_d3crypt3d_cAAA 158 | [+] iter 23 using 0 for dyn4m1cally_d3crypt3d_c0AA 159 | [+] iter 24 using d for dyn4m1cally_d3crypt3d_c0dA 160 | [~] Largest instruction count found to match several others or very close 161 | [~] Locating largest difference from average instead 162 | [+] iter 25 using 3 for dyn4m1cally_d3crypt3d_c0d3 163 | [+] Found pattern dyn4m1cally_d3crypt3d_c0d3 164 | 165 | real 3m26.511s 166 | user 10m53.012s 167 | sys 2m21.344s 168 | ``` 169 | 170 | Some ctf binaries will validate input backwards to throw off fuzzers. using the -rev flag PinCTF is able to alter the input backwards 171 | 172 | ``` 173 | ./pinCTF.py -f $(pwd)/examples/ELF-NoSoftwareBreakpoints -i -sl 25 -rev -t -tc 4 -r abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-@ 174 | [~] Running in reverse direction 175 | [+] iter 24 using S for AAAAAAAAAAAAAAAAAAAAAAAAS 176 | [+] iter 23 using k for AAAAAAAAAAAAAAAAAAAAAAAkS 177 | [+] iter 22 using c for AAAAAAAAAAAAAAAAAAAAAAckS 178 | [+] iter 21 using 0 for AAAAAAAAAAAAAAAAAAAAA0ckS 179 | [+] iter 20 using r for AAAAAAAAAAAAAAAAAAAAr0ckS 180 | [+] iter 19 using _ for AAAAAAAAAAAAAAAAAAA_r0ckS 181 | [+] iter 18 using T for AAAAAAAAAAAAAAAAAAT_r0ckS 182 | [+] iter 17 using N for AAAAAAAAAAAAAAAAANT_r0ckS 183 | [+] iter 16 using i for AAAAAAAAAAAAAAAAiNT_r0ckS 184 | [+] iter 15 using o for AAAAAAAAAAAAAAAoiNT_r0ckS 185 | [+] iter 14 using P for AAAAAAAAAAAAAAPoiNT_r0ckS 186 | [+] iter 13 using k for AAAAAAAAAAAAAkPoiNT_r0ckS 187 | [+] iter 12 using a for AAAAAAAAAAAAakPoiNT_r0ckS 188 | [+] iter 11 using 3 for AAAAAAAAAAA3akPoiNT_r0ckS 189 | [+] iter 10 using r for AAAAAAAAAAr3akPoiNT_r0ckS 190 | [+] iter 9 using B for AAAAAAAAABr3akPoiNT_r0ckS 191 | [+] iter 8 using _ for AAAAAAAA_Br3akPoiNT_r0ckS 192 | [+] iter 7 using e for AAAAAAAe_Br3akPoiNT_r0ckS 193 | [+] iter 6 using r for AAAAAAre_Br3akPoiNT_r0ckS 194 | [+] iter 5 using @ for AAAAA@re_Br3akPoiNT_r0ckS 195 | [+] iter 4 using W for AAAAW@re_Br3akPoiNT_r0ckS 196 | [+] iter 3 using d for AAAdW@re_Br3akPoiNT_r0ckS 197 | [+] iter 2 using r for AArdW@re_Br3akPoiNT_r0ckS 198 | [+] iter 1 using a for AardW@re_Br3akPoiNT_r0ckS 199 | [~] Largest instruction count found to match several others or very close 200 | [~] Locating largest difference from average instead 201 | [+] iter 0 using H for HardW@re_Br3akPoiNT_r0ckS 202 | [+] Found pattern HardW@re_Br3akPoiNT_r0ckS 203 | ``` 204 | -------------------------------------------------------------------------------- /pinCTF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import argparse 6 | import IPython 7 | import configparser 8 | import string 9 | import concurrent.futures 10 | import shutil 11 | from subprocess import PIPE, Popen 12 | from multiprocessing import Pool 13 | 14 | 15 | def main(): 16 | #Defaults 17 | configLocation = "config.ini" 18 | config = checkConfig(configLocation) 19 | 20 | pinLocation = config.get("DEFAULTS","PinLocation") 21 | libraryLocation = config.get("DEFAULTS","LibraryLocation") 22 | count = config.get("DEFAULTS","Count") 23 | seed = config.get("DEFAULTS","Seed") 24 | variable_range = config.get("DEFAULTS","Range") 25 | start = 0 26 | threading = False 27 | 28 | #a-Z 29 | # variable_range = string.ascii_letters 30 | 31 | parser = argparse.ArgumentParser() 32 | 33 | #Add arguments 34 | parser.add_argument('-f','--file',help="file to run pin against") 35 | parser.add_argument('-a', '--arg',help="Trace instructions for passed in argument",action="store_true") 36 | parser.add_argument('-al', '--argLength',help="Trace instructions for passed in argument length",action="store_true") 37 | parser.add_argument('-i', '--input',help="Trace instructions for given input",action="store_true") 38 | parser.add_argument('-il', '--inputLength',help="Trace instructions for input length",action="store_true") 39 | parser.add_argument('-p', '--pinLocation',help="Location of pin's directory") 40 | parser.add_argument('-l', '--pinLibraryLocation',help="Location of pin's instruction0.so libraries") 41 | 42 | #If length based instruction counting you can provide a count 43 | parser.add_argument('-c','--count',help="MaxLength to for length based pin") 44 | 45 | #If arg or input based, we need a seed to start and can use a different 46 | #range to iterate over 47 | parser.add_argument('-s','--seed',help="Initial seed for input or arg pin") 48 | parser.add_argument('-r','--range',help="range of characters to iterate pin over") 49 | parser.add_argument('-rev','--reversed',help="Reverse the direction of guesses",action='store_true') 50 | 51 | #Optionally we can specify a length for our seed, further we can choose where to start guessing 52 | parser.add_argument('-sl','--seedLength',help="Initial seed length for input or arg pin") 53 | parser.add_argument('-st','--seedStart',help="Initial seed index for pin") 54 | 55 | #Speed up process with threading options 56 | parser.add_argument('-t','--threading',help="Enables threading",action='store_true') 57 | parser.add_argument('-tc','--threadCount',help="Number of threads",default=2) 58 | 59 | parser.add_argument('-sk','--skip',help="Skip extra favored paths",action='store_true') 60 | 61 | #Parse Arguments 62 | args =parser.parse_args() 63 | 64 | #Check for argument errors 65 | if not args.file: 66 | print("[-] Error missing file") 67 | exit(0) 68 | if args.file: 69 | args.file = os.path.abspath(args.file) 70 | if not (args.arg or args.argLength or args.input or args.inputLength): #TODO change to A xor B xor C xor D 71 | print("[-] Error missing pin instruction counting technique") 72 | exit(0) 73 | if args.pinLocation: 74 | pinLocation = args.pinLocation 75 | if args.pinLibraryLocation: 76 | libraryLocation = args.pinLibraryLocation 77 | if args.count: 78 | count = int(args.count) 79 | if args.seedLength: 80 | seed = 'A'*int(args.seedLength) 81 | if args.seed: 82 | seed = args.seed 83 | if args.range: 84 | variable_range = args.range 85 | if args.seedStart: 86 | start = int(args.seedStart) 87 | if args.threading: 88 | threading = True 89 | 90 | #Can I get a switch statement please? 91 | if args.argLength: 92 | argLengthTuple = pinLength(pinLocation,libraryLocation,args.file,count,arg=True, multi_core=int(args.threadCount)) 93 | print("[+] Found Length {} : Count {}".format(argLengthTuple[0], argLengthTuple[1])) 94 | 95 | if args.inputLength: 96 | inputLengthTuple = pinLength(pinLocation,libraryLocation,args.file,count,arg=False, multi_core=int(args.threadCount)) 97 | print("[+] Found Num {} : Count {}".format(inputLengthTuple[0], inputLengthTuple[1])) 98 | 99 | if args.arg: 100 | pattern = pinIter(pinLocation,libraryLocation,args.file,seed,variable_range,arg=True,start=start,threading=threading,threadCount=int(args.threadCount),reverseRange=args.reversed,skip=args.skip) 101 | print("[+] Found pattern {}".format(pattern)) 102 | 103 | if args.input: 104 | pattern = pinIter(pinLocation,libraryLocation,args.file,seed,variable_range,arg=False,start=start,threading=threading,threadCount=int(args.threadCount),reverseRange=args.reversed,skip=args.skip) 105 | print("[+] Found pattern {}".format(pattern)) 106 | 107 | #Checks for existence of config 108 | #Creates config if not found, else returns config 109 | def checkConfig(configPath): 110 | config = None 111 | if not os.path.isfile(configPath): 112 | 113 | print("[-] No config found. Building now") 114 | 115 | cwd = os.getcwd() 116 | 117 | config = configparser.ConfigParser() 118 | config.add_section("DEFAULTS") 119 | 120 | #Set defaults if no config is found 121 | config.set("DEFAULTS","PinLocation","") 122 | if os.path.isdir("{}/pin".format(cwd)): 123 | config.set("DEFAULTS","PinLocation","{}/pin".format(cwd)) 124 | 125 | config.set("DEFAULTS","LibraryLocation","") 126 | if os.path.isdir("{}/obj-ia32".format(cwd)): 127 | config.set("DEFAULTS","LibraryLocation","{}/obj-ia32".format(cwd)) 128 | 129 | config.set("DEFAULTS","Count","20") 130 | config.set("DEFAULTS","Seed","ABCD") 131 | config.set("DEFAULTS","Range","abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-") 132 | 133 | configFile = open(configPath,'w') 134 | config.write(configFile) 135 | configFile.close() 136 | else: 137 | config = configparser.ConfigParser() 138 | config.read(configPath) 139 | 140 | return config 141 | 142 | def readCount(fileName="inscount.out"): 143 | inscountFileName = fileName 144 | inscountFile = open(inscountFileName) 145 | line = inscountFile.read() 146 | count = 0 147 | try: 148 | count = int(line.split(' ')[1]) 149 | except: 150 | print("[-] Expected number, got {}".format(line)) 151 | 152 | inscountFile.close() 153 | return count 154 | 155 | def sendPinArgCommand(pin,library,binary,arg): 156 | #The delay given by Popen causes inconsistencies in PIN 157 | #So use os.system instead 158 | COMMAND = "{}/pin -t {}/inscount0.so -- {} {} > /dev/null".format(pin,library,binary,arg) 159 | os.system(COMMAND) 160 | 161 | count = readCount() 162 | return count 163 | 164 | def sendPinInputCommand(pin,library,binary,input): 165 | 166 | #The delay given by Popen causes inconsistencies in PIN 167 | #So use os.system instead 168 | ARGS = "{}/pin -t {}/inscount0.so -- {} ".format(pin,library,binary) 169 | 170 | #Send the output to /dev/null since it will pollute the screen otherwise 171 | os.system("echo {} | {} > /dev/null".format(input,ARGS)) 172 | 173 | count = readCount() 174 | return count 175 | 176 | def sendPinArgCommandThread(pin,library,binary,arg,ident, inIMAP=False): 177 | #The delay given by Popen causes inconsistencies in PIN 178 | #So use os.system instead 179 | 180 | if not os.path.exists("pin_{}".format(ident)): 181 | os.mkdir("pin_{}".format(ident)) 182 | COMMAND = "cd pin_{} > /dev/null; {}/pin -t {}/inscount0.so -- {} {} > /dev/null".format(ident,pin,library,binary,arg) 183 | os.system(COMMAND) 184 | 185 | count = readCount("pin_{}/inscount.out".format(ident)) 186 | shutil.rmtree('pin_{}'.format(ident)) 187 | if not inIMAP: 188 | return count 189 | else: 190 | return ident,count 191 | 192 | def sendPinInputCommandThread(pin,library,binary,input,ident, inIMAP=False): 193 | 194 | #The delay given by Popen causes inconsistencies in PIN 195 | #So use os.system instead 196 | if not os.path.exists("pin_{}".format(ident)): 197 | os.mkdir("pin_{}".format(ident)) 198 | ARGS = "{}/pin -t {}/inscount0.so -- {} ".format(pin,library,binary) 199 | 200 | #Send the output to /dev/null since it will pollute the screen otherwise 201 | COMMAND = "cd pin_{} > /dev/null; echo {} | {} > /dev/null".format(ident,input,ARGS) 202 | os.system(COMMAND) 203 | 204 | count = readCount("pin_{}/inscount.out".format(ident)) 205 | shutil.rmtree('pin_{}'.format(ident)) 206 | if not inIMAP: 207 | return count 208 | else: 209 | return ident,count 210 | 211 | def pinLength(pin,library,binary,length,arg=False, multi_core=1): 212 | lengthDict = {} 213 | arg_list = [] 214 | 215 | if multi_core > 1: 216 | m_pool = Pool(multi_core) 217 | for i in range(1,int(length)+1): 218 | parallel_arg = (pin, library, binary, 'A'*i,'A'*i,i-1, arg) 219 | arg_list.append(parallel_arg) 220 | #(runThreadedCommand,pin,library,binary,path,item,i,arg) 221 | for i in m_pool.imap_unordered(runThreadedCommandWrapper, arg_list): 222 | sys.stdout.write("[~] Trying {}\r".format('A'*len(i[0]))) 223 | sys.stdout.flush() 224 | lengthDict[len(i[0])] = i[1] 225 | 226 | else: 227 | for i in range(1,int(length)+1): 228 | if arg: 229 | count = sendPinArgCommand(pin,library,binary,'A'*(i)) 230 | else: 231 | count = sendPinInputCommand(pin,library, binary, 'A' * (i)) 232 | sys.stdout.write("[~] Trying {}\r".format('A'*(i))) 233 | sys.stdout.flush() 234 | lengthDict[i] = count 235 | 236 | #Get largest count value 237 | largestCount = 0 238 | largestNum = 0 239 | print("{:<4} : {:<15}".format("Num","Instr Count")) 240 | for num in lengthDict: 241 | if lengthDict[num] > largestCount: 242 | largestCount = lengthDict[num] 243 | largestNum = num 244 | print("{:<4} : {:<15}".format(num,lengthDict[num])) 245 | return (largestNum,largestCount) 246 | 247 | def pinIter(pin,library,binary,seed,variable_range,arg=False,start=0,threading=False,threadCount=2,reverseRange=False,skip=False): 248 | 249 | seedLength = len(seed) 250 | 251 | #FavoredPaths help us reset iterations when it's not sure 252 | #whether more or fewer instructions gets the analysis closer 253 | favoredPaths = set() 254 | favoredPaths.add(seed) 255 | 256 | print("[~] Status:\nthreading : {}\nreverseRange : {}\nskipFavoredPaths : {}".format(threading,reverseRange,skip)) 257 | 258 | iterRange = range(start,seedLength) 259 | if reverseRange: 260 | print("[~] Running in reverse direction") 261 | iterRange = reversed(iterRange) 262 | 263 | for i in iterRange: 264 | 265 | rangeDict = {} 266 | 267 | #A copy is made since we modify favoredPaths inside 268 | #the loop 269 | favoredPathsCopy = favoredPaths.copy() 270 | pathIter = 0 271 | favored = True 272 | for path in favoredPathsCopy: 273 | 274 | favored = True 275 | #An unmodified path is needed to remove from the 276 | #Favored path list 277 | originalPath = path 278 | 279 | #This could be parallelized 280 | #Each thread/process could use a unique directory 281 | #And send input from a pool of choices given by 282 | #varaible_range 283 | if threading: 284 | with concurrent.futures.ThreadPoolExecutor(max_workers=threadCount) as executor: 285 | futureToItem = {executor.submit(runThreadedCommand,pin,library,binary,path,item,i,arg): item for item in variable_range} 286 | for future in concurrent.futures.as_completed(futureToItem): 287 | itemInstance = futureToItem[future] 288 | try: 289 | countTuple = future.result() 290 | item = countTuple[0] 291 | count = countTuple[1] 292 | if count == 0: 293 | print("[-] Count was zero for path {}".format(item)) 294 | except Exception as exc: 295 | print('{} had exception {}'.format(itemInstance,exc)) 296 | else: 297 | #Do I need to make a lock? 298 | rangeDict[item] = count 299 | 300 | else: 301 | for item in variable_range: 302 | 303 | #Exchange value in seed for our range values 304 | #Python strings can't do it, so we use a list 305 | 306 | sys.stdout.write("[~] Trying {}\r".format(path)) 307 | sys.stdout.flush() 308 | 309 | seedList = list(path) 310 | seedList[i] = item 311 | path = ''.join(seedList) 312 | if arg: 313 | count = sendPinArgCommand(pin,library,binary,path) 314 | else: 315 | count = sendPinInputCommand(pin,library,binary,path) 316 | rangeDict[item] = count 317 | 318 | 319 | #New line to fix carriage return 320 | print() 321 | 322 | countTuple = getItemByCount(rangeDict) 323 | largestItem = countTuple[0] 324 | largestCount = countTuple[1] 325 | 326 | #Does our largest count match other instructions? 327 | minMatchCount = 3 328 | uniqueCounts = list(rangeDict.values()) 329 | 330 | uniqueUniques = set(rangeDict.values()) 331 | if len(uniqueUniques) == 1: 332 | print("[-] Single unique instruction count") 333 | print("[~] Switching to other favored paths") 334 | print("Removing {}".format(originalPath)) 335 | try: 336 | favoredPaths.remove(originalPath) 337 | except: 338 | print("[-] Error path already gone") 339 | 340 | if len(favoredPaths) > 1: 341 | print("Multiple FavoredPaths : {}".format(len(favoredPaths))) 342 | for favoredPath in favoredPaths: 343 | print(favoredPath) 344 | favored = False 345 | 346 | 347 | 348 | rangeList = list(rangeDict.values()) 349 | average = sum(rangeList) / float(len(rangeList)) 350 | extraPaths = set() 351 | if (uniqueCounts.count(largestCount) > minMatchCount or (largestCount - average) < 4) and favored: 352 | deltaTuple = getItemByDelta(rangeDict) 353 | largestItem = deltaTuple[0] 354 | largestCount = deltaTuple[1] 355 | 356 | #Check for exact matching deltas 357 | deltaDict = deltaTuple[2] 358 | if not skip: 359 | for k,v in deltaDict.items(): 360 | if v == deltaDict[largestItem] and k is not largestItem: 361 | seedList = list(path) 362 | seedList[i] = k 363 | favoredSeed = ''.join(seedList) 364 | extraPaths.add(favoredSeed) 365 | print("[~] Adding delta favored path {}".format(favoredSeed)) 366 | 367 | 368 | 369 | #Slot the value in 370 | if favored: 371 | try: 372 | seedList = list(path) 373 | seedList[i] = largestItem 374 | path = ''.join(seedList) 375 | print("[+] iter {} using {} for {}".format(i,largestItem,path)) 376 | except: 377 | print("[-] Unable to slot value into seed string. Likely 0 delta from other inputs") 378 | print("[~] Switching to other favored paths") 379 | print("Removing {}".format(originalPath)) 380 | favoredPaths.remove(originalPath) 381 | if len(favoredPaths) > 1: 382 | print("Multiple FavoredPaths : {}".format(favoredPaths)) 383 | favored = False 384 | 385 | 386 | if favored: 387 | favoredPaths.clear() 388 | #Building a favored paths list for exploration 389 | if not skip: 390 | for x in uniqueCounts: 391 | if uniqueCounts.count(x) == 1: 392 | temp = "" 393 | for k, v in rangeDict.items(): 394 | if v == x: 395 | temp = k 396 | 397 | seedList = list(path) 398 | seedList[i] = temp 399 | favoredSeed = ''.join(seedList) 400 | favoredPaths.add(favoredSeed) 401 | if len(favoredPaths) > 1: 402 | print("Multiple FavoredPaths : {}".format(favoredPaths)) 403 | for delta in extraPaths: 404 | favoredPaths.add(delta) 405 | extraPaths.clear() 406 | favoredPaths.add(path) 407 | favored = True 408 | else: 409 | print("[+] Ignoring path {}".format(path)) 410 | favored = True 411 | 412 | return favoredPaths.pop() 413 | 414 | def getItemByDelta(rangeDict): 415 | rangeList = list(rangeDict.values()) 416 | print("[~] Largest instruction count found to match several others or very close") 417 | print("[~] Locating largest difference from average instead") 418 | 419 | deltaDict = {} 420 | 421 | #Get largest delta 422 | largestCount = 0 423 | largestItem = 0 424 | 425 | #Get Average 426 | rangeList = list(rangeDict.values()) 427 | average = sum(rangeList) / float(len(rangeList)) 428 | 429 | for k, v in rangeDict.items(): 430 | # print("Key {} : Count {} : Delta {} vs {}".format(k,v,abs(v-average),largestCount)) 431 | if abs(v - average) > largestCount: 432 | largestItem = k 433 | largestCount = abs(v - average) 434 | deltaDict[k] = abs(v - average) 435 | return(largestItem,largestCount,deltaDict) 436 | def getItemByCount(rangeDict): 437 | # Get largest count value 438 | largestCount = 0 439 | largestItem = 0 440 | # print("{:<4} : {:<15}".format("Num","Instr Count")) 441 | #for k, v in rangeDict.items(): 442 | for key in sorted(rangeDict.keys()): 443 | if rangeDict[key] > largestCount: 444 | largestCount = rangeDict[key] 445 | largestItem= key 446 | # print("{:<4} : {:<15}".format(key,rangeDict[key])) 447 | return(largestItem,largestCount) 448 | 449 | def runThreadedCommandWrapper(mapped_data): 450 | return runThreadedCommand(mapped_data[0], mapped_data[1], mapped_data[2], mapped_data[3], mapped_data[4], mapped_data[5], mapped_data[6]) 451 | #(runThreadedCommand,pin,library,binary,path,item,i,arg) 452 | def runThreadedCommand(pin,library,binary,path,item,i,arg=False): 453 | 454 | if len(item) == 1: 455 | seedList = list(path) 456 | seedList[i] = item 457 | path = ''.join(seedList) 458 | else: 459 | seedList = list(path) 460 | seedList[i] = item[0] 461 | path = ''.join(seedList) 462 | 463 | if arg: 464 | count = sendPinArgCommandThread(pin,library,binary,path,item) 465 | else: 466 | count = sendPinInputCommandThread(pin,library,binary,path,item) 467 | return (item,count) 468 | 469 | main() 470 | --------------------------------------------------------------------------------