├── pyioutils ├── README.md └── ioutils.py ├── objcrt_cleaner ├── images │ ├── after.png │ └── before.png ├── README.md └── objcrt_cleaner.py ├── bindiff_cliwrapper ├── README.md └── bindiff.ps1 ├── README.md └── special_func_identifier ├── README.md └── identify_special_functions.py /pyioutils/README.md: -------------------------------------------------------------------------------- 1 | # PYIOUTILS 2 | A simple replacement of pwntools. 3 | -------------------------------------------------------------------------------- /objcrt_cleaner/images/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A7um/CTFUtils/HEAD/objcrt_cleaner/images/after.png -------------------------------------------------------------------------------- /objcrt_cleaner/images/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A7um/CTFUtils/HEAD/objcrt_cleaner/images/before.png -------------------------------------------------------------------------------- /bindiff_cliwrapper/README.md: -------------------------------------------------------------------------------- 1 | # Bindiff CLI Wrapper 2 | 3 | A powershell commnad line wrapper of [Bindiff](https://www.zynamics.com/bindiff.html). 4 | 5 | # Usage 6 | 7 | path/to/Bindiff/bin and path/to/IDA should be added into PATH environement variable. 8 | 9 | bindiff.ps1 primiary secondary. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTFUtils 2 | Useful CTF Utils 3 | 4 | [ObjectiveC Runtime Cleaner](objcrt_cleaner): An IDA script that cleans up annoying and meaningless Objective-C runtime API. 5 | 6 | [Bindiff command line wrapper](bindiff_cliwrapper): A powershell commnad line wrapper of Bindiff. 7 | 8 | [Pyioutils](pyioutils): A simple replacement of pwntools. 9 | 10 | [Special function identifer](special_func_identifier): A simple IDA script that identifies special functions such as nullsub and rename them. 11 | -------------------------------------------------------------------------------- /objcrt_cleaner/README.md: -------------------------------------------------------------------------------- 1 | # ObjectiveC Runtime Cleaner 2 | 3 | 4 | An IDA script that cleans up annoying and meaningless Objective-C runtime API. Current version of script cleans up [_objc_retainAutoreleasedReturnValue, _objc_retainAutoreleaseReturnValue, _objc_retain, _objc_release]. 5 | 6 | The script is based on IDApython microcode API, thus it requires IDA Pro 7.3+. 7 | 8 | ## Usage 9 | 10 | File-> script file -> select "objcrt_cleaner.py" OR copy the content of "objcrt_cleaner.py" into the IDApython console and press Enter. 11 | 12 | Then you can enjoy your cleaner decompiled code. 13 | 14 | ## Demo 15 | 16 | ![Decompiled code without objcrt_cleaner](images/before.png) 17 | ![Decompiled code with objcrt_cleaner](images/after.png) 18 | -------------------------------------------------------------------------------- /special_func_identifier/README.md: -------------------------------------------------------------------------------- 1 | # Special Function Idetifier 2 | 3 | An IDA script that identifies the special functions in a binary and renamed it. 4 | 5 | 6 | ## usage 7 | 8 | File->scirpt file->select identify_special_functions.py 9 | 10 | ## How it works 11 | 12 | 1. Identify functions by pattern matching of decompiled code. 13 | 2. propagated results by call graph recursively 14 | 15 | 16 | 17 | ## Supported Function Type 18 | 19 | 20 | ### nullsub 21 | 22 | ``` 23 | void sub_152D6() 24 | { 25 | ; 26 | } 27 | ``` 28 | 29 | ### identity 30 | 31 | ``` 32 | __int64 __fastcall sub_13516(__int64 a1) 33 | { 34 | return a1; 35 | } 36 | ``` 37 | 38 | ### getvalue 39 | 40 | ``` 41 | __int64 __fastcall getvalue_15f66(__int64 a1) 42 | { 43 | return *(_QWORD *)a1; 44 | } 45 | ``` 46 | 47 | 48 | -------------------------------------------------------------------------------- /objcrt_cleaner/objcrt_cleaner.py: -------------------------------------------------------------------------------- 1 | import ida_idaapi 2 | import ida_hexrays 3 | import idc 4 | objcrt_list = [] 5 | objcrt_list.append(idc.get_name_ea_simple("_objc_retainAutoreleasedReturnValue")) 6 | objcrt_list.append(idc.get_name_ea_simple("_objc_retainAutoreleaseReturnValue")) 7 | objcrt_list.append(idc.get_name_ea_simple("_objc_retain")) 8 | objcrt_list.append(idc.get_name_ea_simple("_objc_release")) 9 | 10 | 11 | class objcrt_cleaner_optinsn_t(ida_hexrays.optinsn_t): 12 | 13 | def __init__(self): 14 | ida_hexrays.optinsn_t.__init__(self) 15 | 16 | def visit_subcall(self,ins): 17 | callins=ins.l.d 18 | if(callins.l.t==0x6 and callins.l.g in objcrt_list): 19 | if(callins.d.f==None): 20 | return 0 21 | else: 22 | ins.l=ida_hexrays.mop_t(callins.d.f.args[0]) 23 | return 1 24 | 25 | return 0 26 | def visit_call(self,callins): 27 | if(callins.l.t==0x6 and callins.l.g in objcrt_list): 28 | if(callins.d.f==None): 29 | return 0 30 | elif(callins.d.f.return_regs.reg.dstr()==''): 31 | callins._make_nop() 32 | return 1 33 | return 0 34 | def func(self,blk,ins,optflag): 35 | cnt=0 36 | if(optflag&ida_hexrays.OPTI_NO_LDXOPT==ida_hexrays.OPTI_NO_LDXOPT): 37 | return 0 38 | if(ins.opcode==ida_hexrays.m_call): 39 | cnt = self.visit_call(ins) 40 | elif(ins.contains_opcode(ida_hexrays.m_call)): 41 | cnt = self.visit_subcall(ins) 42 | if cnt != 0: 43 | blk.mba.verify(True) 44 | return cnt 45 | 46 | oc = objcrt_cleaner_optinsn_t() 47 | oc.install() 48 | -------------------------------------------------------------------------------- /bindiff_cliwrapper/bindiff.ps1: -------------------------------------------------------------------------------- 1 | 2 | if ($args.Count -lt 2){ 3 | Write-Host "bindiff.ps1 primiary secondary" 4 | exit 5 | } 6 | $primiary_file=$args[0].Split("/")[-1] 7 | $secondary_file=$args[1].Split("/")[-1] 8 | Write-Host -NoNewline "The primiary file is " 9 | Write-Host -ForegroundColor red $primiary_file 10 | Write-Host -NoNewline "The secondary file is " 11 | Write-Host -ForegroundColor red $secondary_file 12 | if (-not (Test-Path $primiary_file".i64")){ 13 | Write-Host -NoNewline "Generating " 14 | Write-Host -ForegroundColor red $primiary_file".i64" 15 | idat64.exe -B $primiary_file; 16 | Remove-Item $primiary_file".asm" 17 | } 18 | if (-not (Test-Path $secondary_file".i64")){ 19 | Write-Host -NoNewline "Generating " 20 | Write-Host -ForegroundColor red $secondary_file".i64" 21 | idat64.exe -B $secondary_file; 22 | Remove-Item $secondary_file".asm" 23 | } 24 | if (-not (Test-Path $primiary_file".BinExport")){ 25 | Write-Host -NoNewline "Generating " 26 | Write-Host -ForegroundColor red $primiary_file".BinExport" 27 | idat64.exe -A -OBinExportAutoAction:BinExportBinary -OBinExportModule:$primiary_file".BinExport" $primiary_file 28 | } 29 | 30 | if (-not (Test-Path $secondary_file".BinExport")){ 31 | Write-Host -NoNewline "Generating " 32 | Write-Host -ForegroundColor red $secondary_file".BinExport" 33 | idat64.exe -A -OBinExportAutoAction:BinExportBinary -OBinExportModule:$secondary_file".BinExport" $secondary_file 34 | } 35 | if (-not (Test-Path $primiary_file"_vs_"$secondary_file".BinDiff")){ 36 | Write-Host -NoNewline "Generating " 37 | Write-Host -ForegroundColor red $primiary_file"_vs_"$secondary_file".BinDiff" 38 | bindiff.exe --primary $primiary_file".BinExport" --secondary $secondary_file".BinExport" 39 | } 40 | Write-Host -NoNewline "Final result is " 41 | Write-Host -ForegroundColor red $primiary_file"_vs_"$secondary_file".BinDiff"; 42 | -------------------------------------------------------------------------------- /special_func_identifier/identify_special_functions.py: -------------------------------------------------------------------------------- 1 | import ida_funcs 2 | import ida_name 3 | import ida_xref 4 | import idautils 5 | import idaapi 6 | import ida_allins 7 | from enum import Enum 8 | 9 | from typing import Dict 10 | 11 | 12 | class FUNCTION_TYPE(str, Enum): 13 | NULLSUB = ("nullsub",) 14 | IDENTITY = ("identity",) 15 | GETVALUE = ("getvalue",) 16 | OTHER = ("unknown",) 17 | 18 | 19 | # dummy 20 | visited: Dict[int, bool] = {} 21 | 22 | 23 | ''' 24 | infer function by heuristics rules 25 | ea: function address 26 | func_type: type of the function that ea called to 27 | ''' 28 | def infer_function_type(ea: int, callee_type:FUNCTION_TYPE) -> FUNCTION_TYPE: 29 | 30 | func = ida_funcs.get_func(ea) 31 | if func == None: 32 | return FUNCTION_TYPE.OTHER 33 | func_size = func.end_ea - func.start_ea 34 | # function larger than 0x30 bytes is type other 35 | if func_size > 0x30: 36 | return FUNCTION_TYPE.OTHER 37 | try: 38 | code = str(idaapi.decompile(ea)) 39 | except: 40 | print(f"decompile failed for func {hex(ea)}") 41 | return FUNCTION_TYPE.OTHER 42 | code_lines = code.split("\n") 43 | # function more than five lines is type other 44 | if len(code_lines) > 5: 45 | return FUNCTION_TYPE.OTHER 46 | # nullsub function only have a single ';' 47 | if code_lines[2] == " ;": 48 | return FUNCTION_TYPE.NULLSUB 49 | # if there is only a return subXXX() statement, then it's type depends on its callee 50 | if code_lines[2].find("return") > 0 and callee_type != FUNCTION_TYPE.OTHER: 51 | return callee_type 52 | # if there is only a return a1 statement, then it's type is identity 53 | if code_lines[2].find("return a1;") > 0: 54 | return FUNCTION_TYPE.IDENTITY 55 | # if there is only a return *a1 statement, then it's type is getvalue 56 | # todo: dealing with more getvalue type rather than QWORD 57 | if code_lines[2].find("return *(_QWORD *)a1;") > 0: 58 | return FUNCTION_TYPE.GETVALUE 59 | 60 | return FUNCTION_TYPE.OTHER 61 | 62 | ''' 63 | propagating function type bottom up by call graph 64 | ea: function address 65 | func_type: type of the function 66 | ''' 67 | def propagate_function_type(ea: int, func_type: FUNCTION_TYPE): 68 | print(f"propagate func {hex(ea)}, type {func_type}") 69 | 70 | if visited.get(ea) is not None: 71 | return 72 | if func_type == FUNCTION_TYPE.NULLSUB: 73 | ida_name.set_name(ea, f"nullsub_{hex(ea)[2:]}") 74 | elif func_type == FUNCTION_TYPE.IDENTITY: 75 | ida_name.set_name(ea, f"identity_{hex(ea)[2:]}") 76 | elif func_type == FUNCTION_TYPE.GETVALUE: 77 | ida_name.set_name(ea, f"getvalue_{hex(ea)[2:]}") 78 | 79 | visited[ea] = True 80 | cref = ida_xref.get_first_cref_to(ea) 81 | while cref != idaapi.BADADDR: 82 | cref_func = ida_funcs.get_func(cref) 83 | #function is not defined or function has been visited 84 | if cref_func is None or visited.get(cref_func.start_ea) is not None: 85 | cref = ida_xref.get_next_cref_to(ea, cref) 86 | continue 87 | cref_func_type = infer_function_type(cref_func.start_ea, func_type) 88 | if cref_func_type == FUNCTION_TYPE.OTHER: 89 | cref = ida_xref.get_next_cref_to(ea, cref) 90 | visited[cref_func.start_ea] = True 91 | continue 92 | propagate_function_type(cref_func.start_ea, cref_func_type) 93 | cref = ida_xref.get_next_cref_to(ea, cref) 94 | 95 | 96 | 97 | def main(): 98 | for ea in Functions(): 99 | func_type = infer_function_type(ea, FUNCTION_TYPE.OTHER) 100 | if func_type == FUNCTION_TYPE.OTHER: 101 | continue 102 | 103 | propagate_function_type(ea, func_type) 104 | 105 | 106 | if __name__ == "__main__": 107 | main() 108 | -------------------------------------------------------------------------------- /pyioutils/ioutils.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import threading, sys, os 3 | import socket 4 | import struct 5 | import binascii 6 | import base64 7 | 8 | version = 1.2 9 | ''' 10 | this is a self defined class as a simple pwntool replacement 11 | when there is not intalled pwntool 12 | ''' 13 | 14 | def _to_bytes(data,encoding = "latin-1"): 15 | if sys.version_info.major==3 and type(data)==str : 16 | print("BytesWarning: Text is not bytes; assuming ASCII, no guarantees.") 17 | data = data.encode(encoding) 18 | return data 19 | 20 | def u64(data): 21 | data = _to_bytes(data) 22 | return struct.unpack("Q",data)[0] 44 | 45 | def u32l(data): 46 | data = _to_bytes(data) 47 | return struct.unpack(">I",data)[0] 48 | 49 | def u16l(data): 50 | data = _to_bytes(data) 51 | return struct.unpack(">H",data)[0] 52 | 53 | def p64l(data): 54 | return struct.pack(">Q",data) 55 | 56 | def p32l(data): 57 | return struct.pack(">I",data) 58 | 59 | def p16l(data): 60 | return struct.pack(">H",data) 61 | 62 | def b64e(data): 63 | return base64.b64encode(data) 64 | 65 | def b64d(data): 66 | return base64.b64decode(data) 67 | 68 | def a2h(data): 69 | return binascii.hexlify(data) 70 | 71 | def h2a(data): 72 | return binascii.unhexlify(data) 73 | 74 | 75 | class socket_io(object): 76 | 77 | def sendline(self, data): 78 | data = _to_bytes(data) 79 | return self.sock.send(data+b"\n") 80 | 81 | def send(self, data): 82 | data = _to_bytes(data) 83 | return self.sock.send(data) 84 | 85 | def recv(self, count): 86 | return self.sock.recv(count) 87 | 88 | def recvuntil(self, delims): 89 | buf = b'' 90 | while delims not in buf: 91 | buf += self.recv(1) 92 | return buf 93 | 94 | def recvline(self): 95 | return self.recvuntil(b"\n") 96 | 97 | def recvline_startswith(self, delims): 98 | buf = b'' 99 | while b'\n' + delims not in buf: 100 | buf += self.recv(1) 101 | 102 | while True: 103 | tmp = self.recv(1) 104 | buf += tmp 105 | if buf == b'\n': 106 | break 107 | return buf 108 | def close(self): 109 | self.sock.close() 110 | 111 | def interactive(self): 112 | print ('Switching to interative mode') 113 | go = threading.Event() 114 | def recv_thread(): 115 | while not go.isSet(): 116 | try: 117 | cur = self.recv(1) 118 | if sys.version_info.major==3: 119 | sys.stdout.buffer.write(cur) 120 | else: 121 | sys.stdout.write(cur) 122 | sys.stdout.flush() 123 | except EOFError: 124 | print ('Got EOF while reading in interactive') 125 | break 126 | t = threading.Thread(target = recv_thread) 127 | t.setDaemon(True) 128 | t.start() 129 | while self.sock: 130 | print ('$ '), 131 | while True: 132 | if sys.version_info.major==3: 133 | data = sys.stdin.buffer.read(1) 134 | else: 135 | data = sys.stdin.read(1) 136 | self.send(data) 137 | if data == b'\n': 138 | break 139 | 140 | class remote(socket_io): 141 | def __init__(self, ip, port): 142 | self.sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM) 143 | self.sock.connect((ip,port)) 144 | 145 | 146 | class process: 147 | 148 | def __init__(self, cmd): 149 | self.pipe = subprocess.Popen(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE) 150 | 151 | def sendline(self, data): 152 | data = _to_bytes(data) 153 | return self.pipe.stdin.write(data + b'\n') 154 | 155 | def send(self, data): 156 | data = _to_bytes(data) 157 | return self.pipe.stdin.write(data) 158 | 159 | def recv(self, count): 160 | return self.pipe.stdout.read(count) 161 | 162 | def recvline(self): 163 | return self.pipe.stdout.readline() 164 | 165 | def recvuntil(self, delims): 166 | buf = b'' 167 | while delims not in buf: 168 | buf += self.recv(1) 169 | return buf 170 | 171 | def recvline_startswith(self, delims): 172 | buf = b'' 173 | while b'\n' + delims not in buf: 174 | buf += self.recv(1) 175 | 176 | while True: 177 | tmp = self.recv(1) 178 | buf += tmp 179 | if buf == b'\n': 180 | break 181 | return buf 182 | 183 | def close(self): 184 | try: 185 | self.pipe.kill() 186 | except OSError: 187 | pass 188 | def active(self): 189 | go = threading.Event() 190 | def recv_thread(): 191 | while not go.isSet(): 192 | try: 193 | cur = self.recv(1) 194 | except EOFError: 195 | print ('Got EOF while reading in interactive') 196 | break 197 | t = threading.Thread(target = recv_thread) 198 | t.setDaemon(True) 199 | t.start() 200 | 201 | def interactive(self): 202 | print ('Switching to interative mode') 203 | go = threading.Event() 204 | def recv_thread(): 205 | while not go.isSet(): 206 | try: 207 | cur = self.recv(1) 208 | if sys.version_info.major==3: 209 | sys.stdout.buffer.write(cur) 210 | else: 211 | sys.stdout.write(cur) 212 | sys.stdout.flush() 213 | except EOFError: 214 | print ('Got EOF while reading in interactive') 215 | break 216 | t = threading.Thread(target = recv_thread) 217 | t.setDaemon(True) 218 | t.start() 219 | while self.pipe: 220 | print ('$ '), 221 | while True: 222 | if sys.version_info.major==3: 223 | data = sys.stdin.buffer.read(1) 224 | else: 225 | data = sys.stdin.read(1) 226 | self.send(data) 227 | if data == b'\n': 228 | break 229 | 230 | class conn(socket_io): 231 | def __init__(self, sock, addr): 232 | self.addr=addr 233 | self.sock=sock 234 | 235 | class server: 236 | def __init__(self, ip, port): 237 | self.sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM) 238 | self.sock.bind((ip,port)) 239 | self.sock.listen(5) 240 | 241 | def get_conn(self): 242 | sock,addr=self.sock.accept() 243 | return conn(sock,addr) 244 | --------------------------------------------------------------------------------