├── README.md ├── bin └── win32 │ └── REProgram.plw └── src └── reprogram.cpp /README.md: -------------------------------------------------------------------------------- 1 | ##What is it 2 | 3 | - A plugin for IDA Pro 6.1 and higher (and lower?) 4 | - A way of making almost-arbitrary changes to an executable when run under a debugger -- even changes that don't fit 5 | - A portmanteau of "reverse-engineering" and "reprogram" 6 | 7 | ##Installation 8 | 9 | Drag the compiled plugin into the IDA "plugins" directory 10 | 11 | ##Usage 12 | 13 | 1. Select the region of code you wish to replace, and run REProgram from the Plugins menu or press Alt+F2. 14 | 2.In the prompt that pops up, enter the (possibly empty) code that you wish to run instead of the 15 | selected code. 16 | 3. To return the region to normal, place your cursor anywhere 17 | within the reprogrammed region and run the plugin again. 18 | 19 | A list of all reprogrammed regions is available under the View menu. 20 | 21 | ##What's Possible 22 | 23 | 24 | REProgram has two modes of working. If the assembly you type in is not 25 | larger than the original selection, it will behave essentially the same as 26 | if you patched the original executable. When you run the program in the 27 | debugger, REProgram will replace the code in the selection with the 28 | provided code, and fill in any remaining space with NOPs. As a bonus, 29 | using REProgram to modify data segments also works in this case. 30 | 31 | If the assembly you type in is larger than the original selection, when 32 | control reaches a reprogrammed region, REProgram will place as many 33 | instructions in the region as it can, and run control through that space 34 | over and over until all the desired instructions have been executed 35 | control passes outside of the region. In this case, jumps to the inside of 36 | the reprogrammed region are not guaranteed to work, although jumps from 37 | the region to the outside are. Note that, as REProgram uses breakpoints to 38 | implement this behavior, focus will return to IDA every time a region 39 | reprogrammed in this manner is hit; minimizing IDA is recommended. 40 | 41 | ##What's Not 42 | 43 | Only x86 is supported. REProgram uses IDA's onboard assembler, and suffers 44 | from all its shortcomings. One workaround is to use the db directive to 45 | specify an instruction in raw machine language 46 | 47 | REProgram cannot handle the case where there is an instruction larger than 48 | the reprogrammed region it is meant to fit in; this can typically be 49 | overcome just be widening the region to include adjacent instructions and 50 | adding them to the reprogramming code. 51 | 52 | ##History 53 | 54 | REProgram is a successor to nopper, which simulated nopping out code using breakpoints. nopper (and some nifty screenshots) are available at http://code.google.com/p/nopper/ 55 | 56 | REProgram was entered in the 2011 Hex-Rays Plugin Contest: http://www.hex-rays.com/contests/2011/index.shtml -------------------------------------------------------------------------------- /bin/win32/REProgram.plw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkoppel/REProgram/caf67d1d69d68461b0529cabf914876fd32ce3e7/bin/win32/REProgram.plw -------------------------------------------------------------------------------- /src/reprogram.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 James Koppel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file excfept in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * REProgram enables you to almost-arbitrarily alter areas of an executable 19 | * when run under the debugger. 20 | */ 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | using namespace std; 41 | 42 | /* 43 | * According to the Internet, x86 gives an error if an instruction exceeds 15 bytes, 44 | * though the spec allows them to grow slightly longer 45 | */ 46 | #define MAX_INSTRUCTION_LENGTH 20 47 | #define MAX_INPUT_LENGTH 100000 48 | #define MAX_DISASM_LENGTH 500000 49 | 50 | #define NOP_BYTE (0x90) 51 | 52 | typedef map*>* > > repmap; 53 | 54 | repmap replacements; 55 | set disabled_addrs; 56 | 57 | set start_bpts; 58 | map end_for_start; 59 | map > midreprgm_bpts; 60 | 61 | bgcolor_t disabled_col = 0x00222222; 62 | bgcolor_t overridden_col = 0x0000CC00; 63 | 64 | const char* node_name = "$ REProgram addresses"; 65 | 66 | netnode *prim_node = NULL; 67 | 68 | char asm_disasm[MAX_DISASM_LENGTH]; 69 | 70 | /* 71 | * These exist since VC7 appears to forbid references to stack variables 72 | * being passed around 73 | */ 74 | char *next_token = NULL; 75 | char *asmt; 76 | 77 | /* 78 | * Also returns a disassembly 79 | * of that assembly, to see what it was actually assembled to 80 | */ 81 | int assemble_line(ea_t ea, const char *line, uchar *bin, char* buf=NULL, size_t bufsize=0) { 82 | 83 | int size = ph.notify(processor_t::assemble, ea, 0, ea, true, line, bin); 84 | 85 | for(int i = 0; i < size; i++) { 86 | patch_byte(ea+i, bin[i]); 87 | } 88 | 89 | if(buf != NULL) { 90 | generate_disasm_line(ea, buf, bufsize); 91 | } 92 | 93 | for(ea_t e = ea; e < ea + size; e++) { 94 | patch_byte(e, get_original_byte(e)); 95 | } 96 | 97 | return size; 98 | } 99 | 100 | void place_next_reprogrammed_insns(ea_t start, int first_insn) { 101 | ea_t end = replacements[start].first; 102 | vector*>* ovec = replacements[start].second; 103 | 104 | ea_t e = start; 105 | 106 | bool fits = true; 107 | 108 | int last_insn = first_insn; 109 | 110 | 111 | for(vector*>::iterator ovit = ovec->begin() + first_insn; 112 | ovit != ovec->end(); 113 | ovit++) { 114 | vector* ivec = *ovit; 115 | 116 | if(e+ivec->size() > end) { 117 | fits = false; 118 | break; 119 | } 120 | 121 | for(vector::iterator ivit = ivec->begin(); ivit != ivec->end(); ivit++) { 122 | put_dbg_byte(e, *ivit); 123 | //msg("putting %d at %a\n", *ivit, e); 124 | e++; 125 | } 126 | 127 | last_insn++; 128 | } 129 | 130 | if(fits) { 131 | for(; e < end; e++) { 132 | put_dbg_byte(e, NOP_BYTE); 133 | } 134 | 135 | } else { 136 | midreprgm_bpts[e] = make_pair(start, last_insn); 137 | end_for_start[start] = e; 138 | 139 | bool r1 = request_add_bpt(e); 140 | } 141 | 142 | invalidate_dbgmem_contents(BADADDR, 0); 143 | 144 | } 145 | 146 | //-------------------------------------------------------------------------- 147 | static int idaapi dbg_callback(void * /*user_data*/, int notification_code, va_list va) 148 | { 149 | if(notification_code == dbg_process_start) { 150 | 151 | start_bpts.clear(); 152 | end_for_start.clear(); 153 | midreprgm_bpts.clear(); 154 | 155 | repmap::iterator it; 156 | 157 | for(it = replacements.begin(); it != replacements.end(); it++) { 158 | ea_t start = it->first; 159 | ea_t end = it->second.first; 160 | vector*>* ovec = it->second.second; 161 | 162 | ea_t e = start; 163 | 164 | bool fits = true; 165 | 166 | for(vector*>::iterator ovit = ovec->begin(); ovit != ovec->end(); ovit++) { 167 | vector* ivec = *ovit; 168 | 169 | if(e+ivec->size() > end) { 170 | fits = false; 171 | break; 172 | } 173 | 174 | for(vector::iterator ivit = ivec->begin(); ivit != ivec->end(); ivit++) { 175 | put_dbg_byte(e, *ivit); 176 | //msg("putting %d at %a\n", *ivit, e); 177 | e++; 178 | } 179 | } 180 | 181 | if(fits) { 182 | for(; e < end; e++) { 183 | put_dbg_byte(e, NOP_BYTE); 184 | } 185 | } else { 186 | start_bpts.insert(start); 187 | request_add_bpt(start); 188 | } 189 | 190 | run_requests(); 191 | 192 | } 193 | } else if(notification_code == dbg_bpt) { 194 | thid_t tid = va_arg(va, thid_t); 195 | ea_t addr = va_arg(va, ea_t); 196 | 197 | if(start_bpts.count(addr) > 0) { 198 | 199 | if(end_for_start.count(addr) > 0) { 200 | if(exist_bpt(addr)) 201 | request_del_bpt(end_for_start[addr]); 202 | end_for_start.erase(end_for_start.find(addr)); 203 | } 204 | 205 | place_next_reprogrammed_insns(addr, 0); 206 | bool r1 = request_continue_process(); 207 | } else if(midreprgm_bpts.count(addr) > 0) { 208 | request_del_bpt(addr); 209 | 210 | 211 | ea_t start = midreprgm_bpts[addr].first; 212 | int next_insn = midreprgm_bpts[addr].second; 213 | midreprgm_bpts.erase(midreprgm_bpts.find(addr)); 214 | 215 | if(end_for_start.count(start) > 0) { 216 | end_for_start.erase(end_for_start.find(start)); 217 | } 218 | 219 | place_next_reprogrammed_insns(start, next_insn); 220 | 221 | regval_t v; 222 | v.rvtype = RVT_INT; 223 | v.ival = start; 224 | bool r1 = request_set_reg_val("EIP", &v); 225 | bool r2 = request_continue_process(); 226 | } 227 | run_requests(); 228 | } else if(notification_code == dbg_process_exit) { 229 | for(set::iterator it = start_bpts.begin(); it != start_bpts.end(); it++) { 230 | request_del_bpt(*it); 231 | if(end_for_start.count(*it) > 0) { 232 | end_for_start.erase(end_for_start.find(*it)); 233 | } 234 | } 235 | run_requests(); 236 | } 237 | 238 | return 0; 239 | } 240 | 241 | static int idaapi idp_callback(void * /*user_data*/, int notification_code, va_list va) { 242 | 243 | if(notification_code == processor_t::get_bg_color) { 244 | ea_t addr = va_arg(va, ea_t); 245 | bgcolor_t *col = va_arg(va, bgcolor_t*); 246 | 247 | if(disabled_addrs.count(addr)) { 248 | if(replacements.count(addr)) { 249 | *col = overridden_col; 250 | } else { 251 | *col = disabled_col; 252 | } 253 | 254 | return 2; 255 | } else { 256 | return 0; 257 | } 258 | } else { 259 | return 0; 260 | } 261 | } 262 | 263 | ea_t find_reprogrammed_head(ea_t mid) { 264 | 265 | ea_t e; 266 | 267 | for(e = mid; replacements.count(e) == 0; e--); 268 | 269 | return e; 270 | } 271 | 272 | void revert_segment(ea_t start) { 273 | 274 | ea_t end = replacements[start].first; 275 | 276 | for(vector*>::iterator it = replacements[start].second->begin(); 277 | it != replacements[start].second->end(); 278 | it++) { 279 | 280 | delete *it; 281 | } 282 | 283 | delete replacements[start].second; 284 | 285 | replacements.erase(replacements.find(start)); 286 | 287 | disabled_addrs.erase(disabled_addrs.find(start), disabled_addrs.find(end)); 288 | 289 | set_manual_insn(start, ""); 290 | 291 | netnode repnode(prim_node->altval(start)); 292 | repnode.kill(); 293 | prim_node->altdel(start); 294 | } 295 | 296 | void reprogram_segment(ea_t start, ea_t end) { 297 | 298 | asmt = asktext(MAX_INPUT_LENGTH, NULL, "", "Asm: "); 299 | 300 | if(asmt == NULL) { 301 | return; 302 | } 303 | 304 | for(ea_t e = start; e < end; e++) { 305 | disabled_addrs.insert(e); 306 | } 307 | 308 | memset(asm_disasm, '\0', sizeof(asm_disasm)); 309 | char* line_disasm_buf = (char*)calloc(MAXSTR, sizeof(char)); 310 | 311 | vector*>* vec = new vector*>; 312 | netnode repnode; 313 | repnode.create(); 314 | 315 | bool succ = true; 316 | bool asm_error = false; 317 | 318 | nodeidx_t idx = 0; 319 | 320 | for(char *tok = strtok_s(asmt, "\n", &next_token); 321 | tok != NULL; 322 | tok = strtok_s(NULL, "\n", &next_token)) { 323 | 324 | 325 | uchar *assembled_buf = (uchar*)calloc(MAX_INSTRUCTION_LENGTH, sizeof(uchar)); 326 | int size = assemble_line(start, tok, assembled_buf, line_disasm_buf, MAXSTR); 327 | 328 | //Potential Schlemiel-the-painter algorithm, depending on implementation of qstrncat 329 | qstrncat(asm_disasm, line_disasm_buf, MAX_DISASM_LENGTH); 330 | qstrncat(asm_disasm, "\n", MAX_DISASM_LENGTH); 331 | 332 | if(size <= 0) { 333 | succ = false; 334 | asm_error = true; 335 | } 336 | 337 | if((unsigned int)size > end - start) { 338 | succ = false; 339 | } 340 | 341 | repnode.altset(idx, size); 342 | idx++; 343 | 344 | vector* v = new vector; 345 | for(int i = 0; i < size; i++) { 346 | v->push_back(assembled_buf[i]); 347 | repnode.altset(idx, assembled_buf[i]); 348 | idx++; 349 | } 350 | 351 | free(assembled_buf); 352 | 353 | vec->push_back(v); 354 | } 355 | 356 | free(line_disasm_buf); 357 | 358 | set_manual_insn(start, asm_disasm); 359 | 360 | replacements[start] = make_pair(end, vec); 361 | 362 | 363 | repnode.hashset("length", vec->size()); 364 | repnode.hashset("end", end); 365 | prim_node->altset(start, (nodeidx_t)repnode); 366 | 367 | if(!succ) { 368 | if(!asm_error) { 369 | info("The selection cannot be reprogrammed with the provided assembly " 370 | "because one of the instructions is larger than the entire selection."); 371 | } 372 | revert_segment(start); 373 | } 374 | } 375 | 376 | 377 | void handle_segment(ea_t start, ea_t end) { 378 | 379 | bool any_enabled = false; 380 | 381 | for(ea_t ea = start; ea < end; ea = next_not_tail(ea)) { 382 | if(disabled_addrs.count(ea)) { 383 | revert_segment(find_reprogrammed_head(ea)); 384 | any_enabled = true; 385 | } 386 | } 387 | 388 | if(any_enabled) { 389 | return; 390 | } 391 | 392 | reprogram_segment(start, end); 393 | } 394 | 395 | //-------------------------------------------------------------------------- 396 | void idaapi run(int /*arg*/) 397 | { 398 | ea_t start, end; 399 | ea_t scr = get_screen_ea(); 400 | 401 | if(read_selection(&start, &end)) { 402 | handle_segment(start, end); 403 | } else if(scr != BADADDR) { 404 | handle_segment(scr, next_not_tail(scr)); 405 | } 406 | } 407 | 408 | static uint32 idaapi view_rep_sizer(void* obj) { 409 | repmap* m = (repmap*)obj; 410 | 411 | return m->size(); 412 | } 413 | 414 | static char* idaapi view_rep_getline(void *obj, uint32 n, char* buf) { 415 | if(n==0) { 416 | qstrncpy(buf, "Address", strlen("Address")+1); 417 | } else { 418 | repmap* m = (repmap*)obj; 419 | 420 | repmap::iterator it; 421 | uint32 i; 422 | for(i = 0, it = m->begin(); i < n-1; it++, i++); 423 | 424 | qsnprintf(buf, 16, "0x%a", it->first); 425 | } 426 | 427 | return buf; 428 | } 429 | 430 | static bool idaapi view_reprogrammed(void* /* udata */) { 431 | int choice = choose((void*)&replacements, 16, view_rep_sizer, view_rep_getline, "Reprogrammed areas"); 432 | 433 | if(choice <= 0) { 434 | return true;; 435 | } 436 | 437 | repmap::iterator it; 438 | int i; 439 | for(i = 0, it = replacements.begin(); i < choice-1; it++, i++); 440 | 441 | ea_t dest = it->first; 442 | jumpto(dest); 443 | 444 | return true; 445 | } 446 | 447 | //-------------------------------------------------------------------------- 448 | int idaapi init(void) { 449 | 450 | if(!hook_to_notification_point(HT_DBG, dbg_callback, NULL)) { 451 | msg("REProgram failed to hook to debugger; plugin not loaded.\n"); 452 | return PLUGIN_SKIP; 453 | } 454 | 455 | if(!hook_to_notification_point(HT_IDP, idp_callback, NULL)) { 456 | unhook_from_notification_point(HT_DBG, dbg_callback, NULL); 457 | msg("REProgram failed to hook to IDA events; plugin not loaded.\n"); 458 | return PLUGIN_SKIP; 459 | } 460 | 461 | if(!add_menu_item("View/RecentScripts","Reprogrammed areas", NULL, SETMENU_APP, view_reprogrammed, NULL)) { 462 | msg("REProgram failed to add its viewer menu item; plugin not loaded\n"); 463 | return PLUGIN_SKIP; 464 | } 465 | 466 | prim_node = new netnode(node_name, 0, true); 467 | 468 | 469 | for(nodeidx_t idx = prim_node->alt1st(); idx != BADNODE; idx = prim_node->altnxt(idx)) { 470 | nodeidx_t id = prim_node->altval(idx); 471 | netnode* repnode = new netnode(id); 472 | 473 | int len; 474 | repnode->hashval("length", &len, sizeof(int)); 475 | 476 | ea_t end; 477 | repnode->hashval("end", &end, sizeof(ea_t)); 478 | 479 | 480 | nodeidx_t idx2 = repnode->alt1st(); 481 | 482 | vector*>* vec = new vector*>; 483 | 484 | for(int i = 0; i < len; i++) { 485 | vector* line = new vector; 486 | 487 | uint32 linelen = repnode->altval(idx2); 488 | idx2 = repnode->altnxt(idx2); 489 | 490 | for(uint32 j = 0; j < linelen; j++) { 491 | line->push_back(repnode->altval(idx2)); 492 | idx2 = repnode->altnxt(idx2); 493 | } 494 | 495 | vec->push_back(line); 496 | } 497 | 498 | replacements[idx] = make_pair(end, vec); 499 | 500 | for(ea_t e = idx; e < end; e++) { 501 | disabled_addrs.insert(e); 502 | } 503 | 504 | delete repnode; 505 | } 506 | 507 | 508 | return PLUGIN_KEEP; 509 | } 510 | 511 | //-------------------------------------------------------------------------- 512 | void idaapi term(void) 513 | { 514 | unhook_from_notification_point(HT_DBG, dbg_callback, NULL); 515 | unhook_from_notification_point(HT_IDP, idp_callback, NULL); 516 | replacements.clear(); 517 | start_bpts.clear(); 518 | end_for_start.clear(); 519 | midreprgm_bpts.clear(); 520 | } 521 | 522 | //-------------------------------------------------------------------------- 523 | char wanted_name[] = "Reprogram selection"; 524 | char wanted_hotkey[] = "Alt+F2"; 525 | 526 | 527 | //-------------------------------------------------------------------------- 528 | // 529 | // PLUGIN DESCRIPTION BLOCK 530 | // 531 | //-------------------------------------------------------------------------- 532 | plugin_t PLUGIN = 533 | { 534 | IDP_INTERFACE_VERSION, 535 | PLUGIN_DRAW | PLUGIN_PROC, // plugin flags 536 | init, // initialize 537 | 538 | term, // terminate. this pointer may be NULL. 539 | 540 | run, // invoke plugin 541 | 542 | wanted_name, // long comment about the plugin 543 | // it could appear in the status line 544 | // or as a hint 545 | 546 | wanted_name, // multiline help about the plugin 547 | 548 | wanted_name, // the preferred short name of the plugin 549 | wanted_hotkey // the preferred hotkey to run the plugin 550 | }; 551 | --------------------------------------------------------------------------------