├── ida.py ├── main_function.py ├── process_arguments.py ├── process_file.py ├── process_line.py ├── process_offset.py ├── process_procedure.py └── utility.py /ida.py: -------------------------------------------------------------------------------- 1 | import sys, nil.file 2 | from process_file import process_file 3 | 4 | if len(sys.argv) < 6: 5 | print 'python %s ' % sys.argv[0] 6 | print 'python %s ' % sys.argv[0] 7 | sys.exit(1) 8 | 9 | if len(sys.argv) == 6: 10 | process_file(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]) 11 | else: 12 | process_file(sys.argv[1], sys.argv[2], (sys.argv[3], sys.argv[4]), sys.argv[5], sys.argv[6]) -------------------------------------------------------------------------------- /main_function.py: -------------------------------------------------------------------------------- 1 | import string, utility, sys 2 | from process_offset import process_offset 3 | from process_arguments import process_arguments 4 | 5 | def is_number(input): 6 | try: 7 | int(input) 8 | except: 9 | return False 10 | return True 11 | 12 | def process_main_function(module_name, image_base, new_name, procedure_lines, offsets): 13 | replacements = [ 14 | ('\t', ' '), 15 | (' short ', ' '), 16 | (' far ', ' ') 17 | ] 18 | 19 | operators = ['+', '-', '*'] 20 | 21 | declaration_signature = 'void %s()' % new_name 22 | signature = 'void __declspec(naked) %s()' % new_name 23 | 24 | is_first_line = True 25 | 26 | initialisation_function_name = 'initialise_%s' % new_name 27 | interrupt_function_name = '%s_interrupt' % new_name 28 | 29 | main_function = """%s 30 | { 31 | __asm 32 | { 33 | //Initialisation code: 34 | 35 | cmp module_base, 0 36 | jnz is_already_initialised 37 | 38 | call %s 39 | 40 | is_already_initialised: 41 | 42 | //Actual code starts here: 43 | 44 | """ % (signature, initialisation_function_name) 45 | 46 | intra_module_procedures = set() 47 | 48 | linking_counter = 0 49 | 50 | for original_line in procedure_lines: 51 | line = original_line 52 | 53 | line = utility.shrink(line) 54 | 55 | for target, replacement in replacements: 56 | line = line.replace(target, replacement) 57 | 58 | comment_offset = line.find(';') 59 | if comment_offset != -1: 60 | line = line[0 : comment_offset] 61 | 62 | line = line.strip() 63 | 64 | if len(line) == 0: 65 | continue 66 | 67 | tokens = line.split(' ') 68 | 69 | if len(tokens) == 5 and tokens[1] == '=': 70 | if tokens[2] != 'dword' or tokens[3] != 'ptr': 71 | print 'Unknown definition type: %s' % original_line 72 | return None 73 | 74 | macro = tokens[0] 75 | replacement = tokens[-1] 76 | if replacement[0] == '-': 77 | replacement = '-%s' % replacement[1 : ] 78 | replacements.append(('+%s' % macro, replacement)) 79 | 80 | continue 81 | 82 | elif len(tokens) == 2 and tokens[0] == 'call': 83 | target = tokens[1] 84 | 85 | error = False 86 | 87 | try: 88 | offset = offsets[target] 89 | intra_module_procedures.add((target, offset)) 90 | 91 | except KeyError: 92 | error = True 93 | 94 | if error: 95 | print 'Warning: Was unable to retrieve the offset of the procedure in "%s"' % line 96 | 97 | requires_linking, new_line = process_arguments(line, offsets) 98 | if new_line != line: 99 | print 'Warning: Translated "%s" to "%s"' % (line, new_line) 100 | line = new_line 101 | 102 | uses_offset, offset_output = process_offset(original_line, tokens, offsets) 103 | if uses_offset: 104 | print 'Warning: "offset" keyword used in line "%s", translated to "%s"' % (line, offset_output) 105 | line = offset_output 106 | 107 | for operator in operators: 108 | line = line.replace(operator, ' %s ' % operator) 109 | 110 | indentation = 1 111 | if line[-1] != ':': 112 | indentation = 2 113 | else: 114 | if is_first_line: 115 | continue 116 | 117 | if uses_offset: 118 | requires_linking = True 119 | 120 | main_function += '%s%s\n' % (indentation * '\t', line) 121 | if requires_linking: 122 | print 'Added linker reference %d for line "%s"' % (linking_counter, line) 123 | main_function += '\tlinker_address_%d:\n' % linking_counter 124 | linking_counter += 1 125 | 126 | is_first_line = False 127 | 128 | main_function += '\n\t\t//Instruction address table hack:\n\n' 129 | 130 | ud2_count = 4 131 | marker = '\\x0f\\x0b' * ud2_count 132 | 133 | for i in range(0, ud2_count): 134 | main_function += '\t\tud2\n' 135 | 136 | main_function += '\n' 137 | 138 | for i in range(0, linking_counter): 139 | main_function += '\t\tpush linker_address_%d\n' % i 140 | 141 | main_function += '\t}\n}\n\n' 142 | 143 | variables = 'namespace\n{\n' 144 | variables += '\t//Initialisation variables\n\n' 145 | variables += '\tchar const * module_name = "%s";\n' % module_name 146 | variables += '\tunsigned image_base = %s;\n' % image_base 147 | variables += '\tunsigned module_base = 0;\n\n' 148 | 149 | call_addresses = '' 150 | first = True 151 | for name, offset in intra_module_procedures: 152 | if first: 153 | first = False 154 | else: 155 | call_addresses += ',\n' 156 | call_addresses += '\t\t&%s' % name 157 | variables += '\tunsigned %s = 0x%s;\n' % (name, offset) 158 | 159 | if len(intra_module_procedures) > 0: 160 | variables += '\n' 161 | 162 | variables += '}\n\n' 163 | 164 | headers = [ 165 | 'string', 166 | 'windows.h' 167 | ] 168 | 169 | includes = '' 170 | for header in headers: 171 | includes += '#include <%s>\n' % header 172 | includes += '\n' 173 | 174 | initialisation_function = """void %s() 175 | { 176 | __asm 177 | { 178 | int 3 179 | } 180 | } 181 | 182 | %s; 183 | 184 | void __stdcall %s() 185 | { 186 | module_base = reinterpret_cast(GetModuleHandle(module_name)); 187 | if(module_base == 0) 188 | %s(); 189 | 190 | unsigned * call_addresses[] = 191 | { 192 | %s 193 | }; 194 | 195 | unsigned linking_offset = module_base - image_base; 196 | 197 | for(std::size_t i = 0; i < %d; i++) 198 | { 199 | unsigned & address = *call_addresses[i]; 200 | address += linking_offset; 201 | } 202 | 203 | bool success = false; 204 | 205 | std::string const marker = "%s"; 206 | 207 | char * data_pointer = reinterpret_cast(&%s); 208 | while(true) 209 | { 210 | std::string current_string(data_pointer, marker.size()); 211 | if(current_string == marker) 212 | { 213 | success = true; 214 | break; 215 | } 216 | data_pointer++; 217 | } 218 | 219 | if(!success) 220 | %s(); 221 | 222 | data_pointer += marker.size(); 223 | 224 | for(unsigned i = 0; i < %d; i++) 225 | { 226 | char * label_pointer = *reinterpret_cast(data_pointer + 1); 227 | unsigned * immediate_pointer = reinterpret_cast(label_pointer - 4); 228 | DWORD old_protection; 229 | SIZE_T const patch_size = 4; 230 | if(!VirtualProtect(immediate_pointer, patch_size, PAGE_EXECUTE_READWRITE, &old_protection)) 231 | %s(); 232 | unsigned & address = *immediate_pointer; 233 | address += linking_offset; 234 | DWORD unused; 235 | if(!VirtualProtect(immediate_pointer, patch_size, old_protection, &unused)) 236 | %s(); 237 | data_pointer += 5; 238 | } 239 | } 240 | """ % (interrupt_function_name, declaration_signature, initialisation_function_name, interrupt_function_name, call_addresses, len(intra_module_procedures), marker, new_name, interrupt_function_name, linking_counter, interrupt_function_name, interrupt_function_name) 241 | 242 | return (includes, variables, main_function, initialisation_function) -------------------------------------------------------------------------------- /process_arguments.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | def process_arguments(line, offsets): 4 | requires_linking = False 5 | if line[-1] == ':': 6 | return requires_linking, line 7 | tokens = line.split(' ') 8 | instruction = tokens[0] 9 | input = line[len(instruction) + 1 : ] 10 | arguments = input.split(',') 11 | if len(arguments) == 1: 12 | return requires_linking, line 13 | arguments = map(lambda x: x.strip(), arguments) 14 | for i in range(0, len(arguments)): 15 | argument = arguments[i] 16 | 17 | tokens = argument.split(' ') 18 | if tokens[0] in ['byte', 'word', 'dword'] and len(tokens) >= 3: 19 | tokens[2] = 'ds:%s' % tokens[2] 20 | argument = string.join(tokens, ' ') 21 | else: 22 | target = 'ds:' 23 | if len(argument) >= len(target) and argument[0 : len(target)] == target: 24 | argument = argument[len(target) : ] 25 | offset = argument.find('[') 26 | if offset == -1: 27 | name = argument 28 | try: 29 | address = offsets[name] 30 | argument = 'ds:[0%sh]' % address 31 | requires_linking = True 32 | except KeyError: 33 | pass 34 | else: 35 | name = argument[0 : offset] 36 | if len(name) > 0: 37 | try: 38 | address = offsets[name] 39 | arithmetic_term = argument[offset + 1 : - 1] 40 | argument = 'ds:[%s+0%sh]' % (arithmetic_term, address) 41 | requires_linking = True 42 | except KeyError: 43 | print 'Error: Unable to resolve address in "%s"' % line 44 | sys.exit(1) 45 | else: 46 | argument = 'ds:%s' % argument 47 | arguments[i] = argument 48 | return requires_linking, '%s %s' % (instruction, string.join(arguments, ', ')) -------------------------------------------------------------------------------- /process_file.py: -------------------------------------------------------------------------------- 1 | import nil.file, utility 2 | from process_procedure import process_procedure 3 | from process_line import get_code, extract_data 4 | from process_offset import extract_offsets 5 | 6 | def find_target(lines, target, offset, address_mode): 7 | end = len(lines) 8 | for i in range(offset, end): 9 | line = utility.shrink(lines[i]) 10 | if len(line) == 0: 11 | continue 12 | if address_mode: 13 | section, address, line = extract_data(line) 14 | if address == target: 15 | return i 16 | else: 17 | line = get_code(line) 18 | if line[0 : len(target)] == target: 19 | return i 20 | 21 | return None 22 | 23 | def get_image_base(lines): 24 | for line in lines: 25 | if line.find('Imagebase') == -1: 26 | continue 27 | return '0x%s' % line.split(' ')[-1] 28 | return None 29 | 30 | def get_line(offset): 31 | return offset + 1 32 | 33 | def process_file(module_name, input, target, name, output): 34 | print 'Loading the file' 35 | lines = nil.file.read_lines(input) 36 | if lines == None: 37 | print 'Failed to open %s' % input 38 | return False 39 | 40 | print 'Loaded %d lines' % len(lines) 41 | 42 | image_base = get_image_base(lines) 43 | if image_base == None: 44 | print 'Failed to retrieve the image base of %s' % input 45 | return False 46 | 47 | print 'Creating an offset dictionary for the entire listing' 48 | offsets = extract_offsets(lines) 49 | 50 | if type(target) == tuple: 51 | offset = find_target(lines, target[0], 0, True) 52 | else: 53 | offset = find_target(lines, '%s proc near' % target, 0, False) 54 | if offset == None: 55 | print 'Unable to locate the target in %s' % input 56 | return False 57 | 58 | print 'Discovered the procedure at line %d' % get_line(offset) 59 | 60 | if type(target) == tuple: 61 | end = find_target(lines, target[1], offset, True) 62 | else: 63 | end = find_target(lines, '%s endp' % target, offset, False) 64 | if end == None: 65 | print 'Unable to locate the end of the target in %s' % input 66 | return False 67 | 68 | print 'Discovered the end of the target at line %d' % get_line(end) 69 | 70 | procedure_lines = lines[offset + 1: end] 71 | i = 0 72 | for i in range(0, len(procedure_lines)): 73 | procedure_lines[i] = get_code(procedure_lines[i]) 74 | 75 | print 'Creating the output' 76 | data = process_procedure(module_name, image_base, name, procedure_lines, lines, offsets) 77 | 78 | print 'Writing data to %s' % output 79 | 80 | file = open(output, 'w+b') 81 | file.write(data) 82 | file.close() 83 | 84 | return True -------------------------------------------------------------------------------- /process_line.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def extract_data(original_line): 4 | line = original_line.replace('\t', ' ') 5 | offset = line.find(' ') 6 | if offset == -1: 7 | offset = len(line) 8 | description = line[0 : offset] 9 | line = line[offset + 1: ] 10 | tokens = description.split(':') 11 | if len(tokens) != 2: 12 | print 'Unable to parse description in line "%s"' % original_line 13 | sys.exit(1) 14 | section = tokens[0] 15 | address = tokens[1] 16 | return section, address, line 17 | 18 | def get_code(line): 19 | section, address, line = extract_data(line) 20 | return line -------------------------------------------------------------------------------- /process_offset.py: -------------------------------------------------------------------------------- 1 | import sys, string, utility 2 | from process_line import extract_data 3 | 4 | def process_offset(original_line, tokens, offsets): 5 | uses_offset = False 6 | 7 | i = 0 8 | while i < len(tokens): 9 | token = tokens[i] 10 | if token == 'offset': 11 | uses_offset = True 12 | tokens = tokens[0 : i] + tokens[i + 1 : ] 13 | offset_target = tokens[i] 14 | try: 15 | address = offsets[offset_target] 16 | except KeyError: 17 | print 'Error: Unable to process offset in "%s"' % original_line 18 | sys.exit(1) 19 | tokens[i] = '0%sh' % address 20 | i += 1 21 | 22 | return uses_offset, string.join(tokens) 23 | 24 | def extract_offsets(lines): 25 | keywords = ['proc', 'db', 'dw', 'dd'] 26 | offsets = {} 27 | for line in lines: 28 | if len(line) < 15: 29 | continue 30 | section, address, line = extract_data(line) 31 | line = utility.shrink(line) 32 | tokens = line.split(' ') 33 | if len(tokens) >= 2 and tokens[1] in keywords: 34 | name = tokens[0] 35 | offsets[name] = address 36 | print 'Parsed %d offsets' % len(offsets) 37 | return offsets -------------------------------------------------------------------------------- /process_procedure.py: -------------------------------------------------------------------------------- 1 | import nil.time, sys, string 2 | 3 | from main_function import process_main_function 4 | from generate_code import generate_code 5 | 6 | def process_procedure(module_name, image_base, name, procedure_lines, lines, offsets): 7 | 8 | arguments = string.join(sys.argv) 9 | time_string = nil.time.timestamp() 10 | 11 | comment = """/* 12 | Command line arguments: python %s 13 | Time of generation: %s 14 | */ 15 | 16 | """ % (arguments, time_string) 17 | 18 | includes, variables, main_function, initialisation_function = process_main_function(module_name, image_base, name, procedure_lines, offsets) 19 | 20 | output_units = [ 21 | comment, 22 | includes, 23 | variables, 24 | initialisation_function, 25 | main_function 26 | ] 27 | 28 | output = string.join(output_units, '') 29 | 30 | hacks = [ 31 | ('_exit', 'custom_exit') 32 | ] 33 | 34 | for target, replacement in hacks: 35 | output = output.replace(target, replacement) 36 | 37 | return output -------------------------------------------------------------------------------- /utility.py: -------------------------------------------------------------------------------- 1 | def shrink(line): 2 | target = ' ' 3 | while line.find(target) != -1: 4 | line = line.replace(target, ' ') 5 | return line.strip() --------------------------------------------------------------------------------