radare2/libr/core/cmd_cmp.c

1547 lines
39 KiB
C

/* radare - LGPL - Copyright 2009-2022 - pancake */
#include "r_core.h"
static const char *help_message_ci[] = {
"Usage: ci", "[sil] ([obid])", "Compare two bin objects",
"cis", " 0", "compare symbols with current `ob 1` with given obid (0)",
"cii", " 0", "compare imports",
"cil", " 0", "compare libraries",
NULL
};
static const char *help_msg_cmp[] = {
"Usage: cmp", " [file] [file]", "Compare two ($alias) files, and change $? value",
"cmp", " ls ls.old", "compare contents of given files",
"cmp", " $a $b", "same as above but using alias files",
NULL
};
static const char *help_msg_c[] = {
"Usage:", "c[?dfx] [argument]", " # Compare",
"c", " [string]", "compare a plain with escaped chars string",
"c*", " [string]", "same as above, but printing r2 commands instead",
"c1", " [addr]", "compare byte at addr with current offset",
"c2", "[*] [value]", "compare word at offset with given value",
"c4", "[*] [value]", "compare doubleword at offset with given value",
"c8", "[*] [value]", "compare quadword at offset with given value",
"cat", " [file]", "show contents of file (see pwd, ls)",
"cc", " [at]", "compares in two hexdump columns of block size",
"ccc", " [at]", "same as above, but only showing different lines",
"ccd", " [at]", "compares in two disasm columns of block size",
"ccdd", " [at]", "compares decompiler output (e cmd.pdc=pdg|pdd)",
"cd", " [dir]", "chdir",
// "cc", " [offset]", "code bindiff current block against offset"
// "cD", " [file]", "like above, but using radiff -b",
"cf", " [file]", "compare contents of file at current seek",
"cg", "[?] [o] [file]", "graphdiff current file and [file]",
"ci", "[?] [obid] ([obid2])", "compare two bin-objects (symbols, imports, ...)",
"cl|cls|clear", "", "clear screen, (clear0 to goto 0, 0 only)",
"cmp", " [file] [file]", "compare two files",
"cu", "[?] [addr] @at", "compare memory hexdumps of $$ and dst in unified diff",
"cud", " [addr] @at", "unified diff disasm from $$ and given address",
"cv", "[1248] [hexpairs] @at", "compare 1,2,4,8-byte (silent return in $?)",
"cV", "[1248] [addr] @at", "compare 1,2,4,8-byte address contents (silent, return in $?)",
"cw", "[?][*dqjru] [addr]", "compare memory watchers",
"cx", " [hexpair]", "compare hexpair string (use '.' as nibble wildcard)",
"cx*", " [hexpair]", "compare hexpair string (output r2 commands)",
"cX", " [addr]", "Like 'cc' but using hexdiff output",
NULL
};
R_API void r_core_cmpwatch_free(RCoreCmpWatcher *w) {
free (w->ndata);
free (w->odata);
free (w->cmd);
free (w);
}
R_API R_BORROW RCoreCmpWatcher *r_core_cmpwatch_get(RCore *core, ut64 addr) {
RCoreCmpWatcher *w;
RListIter *iter;
r_list_foreach (core->watchers, iter, w) {
if (addr == w->addr) {
return w;
}
}
return NULL;
}
R_API bool r_core_cmpwatch_add(RCore *core, ut64 addr, int size, const char *cmd) {
RCoreCmpWatcher *cmpw;
bool found = false;
r_return_val_if_fail (core && cmd && size > 0, false);
cmpw = r_core_cmpwatch_get (core, addr);
if (!cmpw) {
cmpw = R_NEW0 (RCoreCmpWatcher);
if (!cmpw) {
return false;
}
cmpw->addr = addr;
} else {
free (cmpw->odata);
free (cmpw->ndata);
free (cmpw->cmd);
found = true;
}
cmpw->size = size;
cmpw->cmd = r_str_new (cmd);
if (!cmpw->cmd) {
free (cmpw);
return false;
}
cmpw->odata = NULL;
cmpw->ndata = malloc (size);
if (!cmpw->ndata) {
r_core_cmpwatch_free (cmpw);
return false;
}
if (r_io_nread_at (core->io, addr, cmpw->ndata, size) != size) {
r_core_cmpwatch_free (cmpw);
return false;
}
// Don't append a duplicate
if (!found) {
r_list_append (core->watchers, cmpw);
}
return true;
}
R_API bool r_core_cmpwatch_del(RCore *core, ut64 addr) {
bool ret = false;
RCoreCmpWatcher *w;
RListIter *iter, *iter2;
if (addr == UT64_MAX) { // match all
r_list_foreach_safe (core->watchers, iter, iter2, w) {
r_list_delete (core->watchers, iter);
ret = true;
}
return ret;
}
// Can't use r_core_cmpwatch_get() here since we need the iter
r_list_foreach_safe (core->watchers, iter, iter2, w) {
if (w->addr == addr) {
/* Only one watcher per address - we can leave early */
r_list_delete (core->watchers, iter);
ret = true;
break;
}
}
return ret;
}
R_API bool r_core_cmpwatch_show(RCore *core, ut64 addr, int mode) {
RListIter *iter;
RCoreCmpWatcher *w;
PJ *pj = NULL;
bool ret = false;
if (mode == 'j') {
pj = r_core_pj_new (core);
if (!pj) {
return false;
}
pj_a (pj);
}
r_list_foreach (core->watchers, iter, w) {
bool changed = w->odata? memcmp (w->odata, w->ndata, w->size): false;
if (addr != UT64_MAX && addr != w->addr) {
continue;
}
switch (mode) {
case '*': // print watchers as r2 commands
r_cons_printf ("cw 0x%08" PFMT64x " %d %s%s\n",
w->addr, w->size, w->cmd,
changed? " # differs": "");
break;
case 'q': // quiet
if (changed) {
r_cons_printf ("0x%08" PFMT64x " has changed\n", w->addr);
}
break;
case 'j': // "cw"
{
char *cmd_output = r_core_cmd_strf (core, "%s %d @%" PFMT64d,
w->cmd, w->size, w->addr);
pj_o (pj);
pj_kn (pj, "addr", w->addr);
pj_kb (pj, "changed", changed);
pj_ks (pj, "cmd", w->cmd);
pj_ks (pj, "cmd_out", r_str_get (cmd_output));
pj_end (pj);
free (cmd_output);
}
break;
default:
r_cons_printf ("0x%08" PFMT64x "%s\n", w->addr, changed? " modified": "");
r_core_cmdf (core, "%s %d @%" PFMT64d, w->cmd, w->size, w->addr);
break;
}
ret = true;
}
if (pj) {
char *out;
pj_end (pj);
out = pj_drain (pj);
r_cons_println (out);
free (out);
}
return ret;
}
static bool update_watcher(RIO *io, RCoreCmpWatcher *w) {
r_return_val_if_fail (io && w, false);
free (w->odata);
w->odata = w->ndata;
w->ndata = malloc (w->size);
if (!w->ndata) {
return false;
}
r_io_read_at (io, w->addr, w->ndata, w->size);
return true;
}
/* Replace old data with current new data, then read IO into new data */
R_API bool r_core_cmpwatch_update(RCore *core, ut64 addr) {
RCoreCmpWatcher *w;
RListIter *iter;
bool ret = false;
if (addr != UT64_MAX) {
w = r_core_cmpwatch_get (core, addr);
if (w) {
return update_watcher (core->io, w);
}
return false;
}
r_list_foreach (core->watchers, iter, w) {
if (update_watcher (core->io, w)) {
ret = true;
}
}
return ret;
}
static bool revert_watcher(RCoreCmpWatcher *w) {
r_return_val_if_fail (w, false);
if (w->odata) {
free (w->ndata);
w->ndata = w->odata;
w->odata = NULL;
}
return true;
}
/* Mark the current old state as new, discarding the original new state */
R_API bool r_core_cmpwatch_revert(RCore *core, ut64 addr) {
RCoreCmpWatcher *w;
RListIter *iter;
bool ret = false;
if (addr != UT64_MAX) {
w = r_core_cmpwatch_get (core, addr);
if (w) {
return revert_watcher (w);
}
return false;
}
r_list_foreach (core->watchers, iter, w) {
if (revert_watcher (w)) {
ret = true;
}
}
return ret;
}
static int radare_compare_words(RCore *core, ut64 of, ut64 od, int len, int ws) {
int i;
bool useColor = r_config_get_i (core->config, "scr.color") != 0;
utAny v0, v1;
RConsPrintablePalette *pal = &r_cons_singleton ()->context->pal;
for (i = 0; i < len; i+=ws) {
memset (&v0, 0, sizeof (v0));
memset (&v1, 0, sizeof (v1));
r_io_read_at (core->io, of + i, (ut8*)&v0, ws);
r_io_read_at (core->io, od + i, (ut8*)&v1, ws);
char ch = (v0.v64 == v1.v64)? '=': '!';
const char *color = useColor? ch == '='? "": pal->graph_false: "";
const char *colorEnd = useColor? Color_RESET: "";
if (useColor) {
r_cons_printf ("%s0x%08" PFMT64x" "Color_RESET, pal->offset, of + i);
} else {
r_cons_printf ("0x%08" PFMT64x" ", of + i);
}
switch (ws) {
case 1:
r_cons_printf ("%s0x%02x %c 0x%02x%s\n", color,
(ut32)(v0.v8 & 0xff), ch, (ut32)(v1.v8 & 0xff), colorEnd);
break;
case 2:
r_cons_printf ("%s0x%04hx %c 0x%04hx%s\n", color,
v0.v16, ch, v1.v16, colorEnd);
break;
case 4:
r_cons_printf ("%s0x%08"PFMT32x" %c 0x%08"PFMT32x"%s\n", color,
v0.v32, ch, v1.v32, colorEnd);
//r_core_cmdf (core, "fd@0x%"PFMT64x, v0.v32);
if (v0.v32 != v1.v32) {
// r_core_cmdf (core, "fd@0x%"PFMT64x, v1.v32);
}
break;
case 8:
r_cons_printf ("%s0x%016"PFMT64x" %c 0x%016"PFMT64x"%s\n",
color, v0.v64, ch, v1.v64, colorEnd);
//r_core_cmdf (core, "fd@0x%"PFMT64x, v0.v64);
if (v0.v64 != v1.v64) {
// r_core_cmdf (core, "fd@0x%"PFMT64x, v1.v64);
}
break;
}
}
return 0;
}
static int radare_compare_unified(RCore *core, ut64 of, ut64 od, int len) {
int i, min, inc = 16;
ut8 *f, *d;
if (len < 1) {
return false;
}
f = malloc (len);
if (!f) {
return false;
}
d = malloc (len);
if (!d) {
free (f);
return false;
}
r_io_read_at (core->io, of, f, len);
r_io_read_at (core->io, od, d, len);
int headers = B_IS_SET (core->print->flags, R_PRINT_FLAGS_HEADER);
if (headers) {
B_UNSET (core->print->flags, R_PRINT_FLAGS_HEADER);
}
for (i = 0; i < len; i += inc) {
min = R_MIN (16, (len - i));
if (!memcmp (f + i, d + i, min)) {
r_cons_printf (" ");
r_print_hexdiff (core->print, of + i, f + i, of + i, f + i, min, 0);
} else {
r_cons_printf ("- ");
r_print_hexdiff (core->print, of + i, f + i, od + i, d + i, min, 0);
r_cons_printf ("+ ");
r_print_hexdiff (core->print, od + i, d + i, of + i, f + i, min, 0);
}
}
if (headers) {
B_SET (core->print->flags, R_PRINT_FLAGS_HEADER);
}
return true;
}
static int radare_compare(RCore *core, const ut8 *f, const ut8 *d, int len, int mode) {
int i, eq = 0;
PJ *pj = NULL;
if (len < 1) {
return 0;
}
if (mode == 'j') {
pj = pj_new ();
if (!pj) {
return -1;
}
pj_o (pj);
pj_k (pj, "diff_bytes");
pj_a (pj);
}
for (i = 0; i < len; i++) {
if (f[i] == d[i]) {
eq++;
continue;
}
switch (mode) {
case 0:
r_cons_printf ("0x%08"PFMT64x " (byte=%.2d) %02x '%c' -> %02x '%c'\n",
core->offset + i, i + 1,
f[i], (IS_PRINTABLE (f[i]))? f[i]: ' ',
d[i], (IS_PRINTABLE (d[i]))? d[i]: ' ');
break;
case '*':
r_cons_printf ("wx %02x @ 0x%08"PFMT64x "\n",
d[i],
core->offset + i);
break;
case 'j':
pj_o (pj);
pj_kn (pj, "offset", core->offset + i);
pj_ki (pj, "rel_offset", i);
pj_ki (pj, "value", (int)f[i]);
pj_ki (pj, "cmp_value", (int)d[i]);
pj_end (pj);
break;
default:
R_LOG_ERROR ("Unknown mode");
break;
}
}
if (mode == 0) {
R_LOG_INFO ("Compare %d/%d equal bytes (%d%%)", eq, len, (eq / len) * 100);
} else if (mode == 'j') {
pj_end (pj);
pj_ki (pj, "equal_bytes", eq);
pj_ki (pj, "total_bytes", len);
pj_end (pj); // End array
pj_end (pj); // End object
r_cons_println (pj_string (pj));
}
return len - eq;
}
static void nowatchers(ut64 addr) {
if (addr == UT64_MAX) {
R_LOG_ERROR ("No watchers exist");
} else {
R_LOG_ERROR ("No watcher exists at address %" PFMT64x, addr);
}
}
/* Returns 0 if operation succeeded, 1 otherwise */
static int cmd_cmp_watcher(RCore *core, const char *input) {
static const char *help_msg_cw[] = {
"Usage: cw", "[args]", "Manage compare watchers; See if and how memory changes",
"cw??", "", "Show more info about watchers",
"cw ", "addr sz cmd", "Add a compare watcher",
"cw", "[*qj] [addr]", "Show compare watchers (*=r2 commands, q=quiet, j=json)",
"cwd", " [addr]", "Delete watcher",
"cwr", " [addr]", "Revert watcher",
"cwu", " [addr]", "Update watcher",
NULL
};
static const char *verbose_help_cw =
"Watchers are used to record memory at 2 different points in time, then\n"
"report if and how it changed. First, create one with `cw addr sz cmd`. This\n"
"will record sz bytes at addr. To record the second state, use `cwu`. Now, when\n"
"you run `cw`, the watcher will report if the bytes changed and run the command given\n"
"at creation with the size and address. You may overwrite any watcher by creating\n"
"another at the same address. This will discard the existing watcher completely.\n"
"\n"
"When you create a watcher, the data read from memory is marked as \"new\". Updating\n"
"the watcher with `cwu` will mark this data as \"old\", and then read the \"new\" data.\n"
"`cwr` will mark the current \"old\" state as being \"new\", letting you reuse it as\n"
"your new base state when updating with `cwu`. Any existing \"new\" state from running\n"
"`cwu` previously is lost in this process. Watched memory areas may overlap with no ill\n"
"effects, but may have unexpected results if you update some but not others.\n"
"\n"
"Showing a watcher without updating will still run the command, but it will not report\n"
"changes.\n"
"\n"
"When an address is an optional argument, the command will apply to all watchers if\n"
"you don't pass one.\n"
"\n"
"For more details and examples, see section 4.10 of the radare2 book.\n";
ut64 addr = UT64_MAX;
int ret = 0;
switch (*input) {
case ' ': { // "cw "
int argc;
char **argv = r_str_argv (input + 1, &argc);
if (!argv) {
return 1;
}
if (argc == 1) { // "cw [addr]"
addr = r_num_math (core->num, argv[0]);
r_core_cmpwatch_show (core, addr, 0);
} else if (argc == 3) { // "cw addr sz cmd"
addr = r_num_math (core->num, argv[0]);
ut64 size = r_num_math (core->num, argv[1]);
if (size < 1) {
ret = 1;
R_LOG_ERROR ("Can't create a watcher with size less than 1");
goto out_free_argv;
}
if (size > INT_MAX) {
ret = 1;
R_LOG_ERROR ("Can't create a watcher with size larger than an int");
goto out_free_argv;
}
if (!r_core_cmpwatch_add (core, addr, (int)size, argv[2])) {
ret = 1;
R_LOG_ERROR ("Failed to add watcher");
}
} else {
r_core_cmd_help_match (core, help_msg_cw, "cw ", true);
}
out_free_argv:
r_str_argv_free (argv);
break;
}
case 'd': // "cwd"
if (input[1] == '?') {
r_core_cmd_help_match (core, help_msg_cw, "cwd", true);
return 0;
}
if (input[1]) {
addr = r_num_math (core->num, input + 2);
}
if (addr == UT64_MAX && !r_cons_yesno ('n', "Delete all watchers? (y/N)")) {
return 1;
}
if (!r_core_cmpwatch_del (core, addr) && addr) {
ret = 1;
nowatchers (addr);
}
break;
case 'r': // "cwr"
if (input[1] == '?') {
r_core_cmd_help_match (core, help_msg_cw, "cwr", true);
return 0;
}
if (input[1]) {
addr = r_num_math (core->num, input + 2);
}
if (addr == UT64_MAX &&
!r_cons_yesno ('n', "Revert all watchers? (y/N)")) {
return 1;
}
if (!r_core_cmpwatch_revert (core, addr)) {
ret = 0;
nowatchers (addr);
}
break;
case 'u': // "cwu"
if (input[1] == '?') {
r_core_cmd_help_match (core, help_msg_cw, "cwu", true);
return 0;
}
if (input[1]) {
addr = r_num_math (core->num, input + 2);
}
if (!r_core_cmpwatch_update (core, addr)) {
ret = 1;
nowatchers (addr);
}
break;
case '*': // "cw*"
case 'q': // "cwq"
case 'j': // "cwj"
case '\0': { // "cw"
int mode = *input;
if (*input && input[1]) {
addr = r_num_math (core->num, input + 2);
}
if (!r_core_cmpwatch_show (core, addr, mode)) {
ret = 1;
/* Skip error message for json, it will still show [] */
if (mode == 'j') {
break;
}
nowatchers (addr);
}
break;
}
case '?': // "cw?"
default:
if (input[1] == '?') {
// this command really needs explaining
// so it's strongly signposted
r_cons_printf ("%s", verbose_help_cw);
} else {
r_core_cmd_help (core, help_msg_cw);
}
break;
}
return ret;
}
static int cmd_cmp_disasm(RCore *core, const char *input, int mode) {
RAsmOp op, op2;
int i, j;
char colpad[80];
int hascolor = r_config_get_i (core->config, "scr.color");
int cols = r_config_get_i (core->config, "hex.cols") * 2;
ut64 off = r_num_math (core->num, input);
ut8 *buf = calloc (core->blocksize + 32, 1);
RConsPrintablePalette *pal = &r_cons_singleton ()->context->pal;
if (!buf) {
return false;
}
r_io_read_at (core->io, off, buf, core->blocksize + 32);
switch (mode) {
case 'd': // decompiler
{
#if 0
char *a = r_core_cmd_strf (core, "pdc @ 0x%"PFMT64x, off);
char *b = r_core_cmd_strf (core, "pdc @ 0x%"PFMT64x, core->offset);
RDiff *d = r_diff_new ();
char *s = r_diff_buffers_unified (d, a, strlen(a), b, strlen(b));
r_cons_printf ("%s\n", s);
free (a);
free (b);
free (s);
r_diff_free (d);
#else
r_core_cmdf (core, "pdc @ 0x%"PFMT64x">$a", off);
r_core_cmdf (core, "pdc @ 0x%"PFMT64x">$b", core->offset);
r_core_cmd0 (core, "diff $a $b;rm $a;rm $b");
#endif
}
break;
case 'c': // columns
for (i = j = 0; i < core->blocksize && j < core->blocksize;) {
// dis A
r_asm_set_pc (core->rasm, core->offset + i);
(void) r_asm_disassemble (core->rasm, &op,
core->block + i, core->blocksize - i);
// dis B
r_asm_set_pc (core->rasm, off + i);
(void) r_asm_disassemble (core->rasm, &op2,
buf + j, core->blocksize - j);
// show output
bool iseq = r_strbuf_equals (&op.buf_asm, &op2.buf_asm);
memset (colpad, ' ', sizeof (colpad));
{
int pos = strlen (r_strbuf_get (&op.buf_asm));
pos = (pos > cols)? 0: cols - pos;
colpad[pos] = 0;
}
if (hascolor) {
r_cons_print (iseq? pal->graph_true: pal->graph_false);
}
r_cons_printf (" 0x%08"PFMT64x " %s %s",
core->offset + i, r_strbuf_get (&op.buf_asm), colpad);
r_cons_printf ("%c 0x%08"PFMT64x " %s\n",
iseq? '=': '!', off + j, r_strbuf_get (&op2.buf_asm));
if (hascolor) {
r_cons_print (Color_RESET);
}
if (op.size < 1) {
op.size = 1;
}
i += op.size;
if (op2.size < 1) {
op2.size = 1;
}
j += op2.size;
}
break;
case 'u': // unified
for (i = j = 0; i < core->blocksize && j < core->blocksize;) {
// dis A
r_asm_set_pc (core->rasm, core->offset + i);
(void) r_asm_disassemble (core->rasm, &op,
core->block + i, core->blocksize - i);
// dis B
r_asm_set_pc (core->rasm, off + i);
(void) r_asm_disassemble (core->rasm, &op2,
buf + j, core->blocksize - j);
// show output
bool iseq = r_strbuf_equals (&op.buf_asm, &op2.buf_asm); // (!strcmp (op.buf_asm, op2.buf_asm));
if (iseq) {
r_cons_printf (" 0x%08"PFMT64x " %s\n",
core->offset + i, r_strbuf_get (&op.buf_asm));
} else {
if (hascolor) {
r_cons_print (pal->graph_false);
}
r_cons_printf ("-0x%08"PFMT64x " %s\n",
core->offset + i, r_strbuf_get (&op.buf_asm));
if (hascolor) {
r_cons_print (pal->graph_true);
}
r_cons_printf ("+0x%08"PFMT64x " %s\n",
off + j, r_strbuf_get (&op2.buf_asm));
if (hascolor) {
r_cons_print (Color_RESET);
}
}
if (op.size < 1) {
op.size = 1;
}
i += op.size;
if (op2.size < 1) {
op2.size = 1;
}
j += op2.size;
}
break;
}
return 0;
}
static int cmd_cp(void *data, const char *input) {
RCore *core = (RCore *)data;
bool use_corefile;
const char *help_msg_cp[] = {
"cp", " src dst", "Standard file copy",
"cp", ".[ext]", "Copy current file <name> to <name>.ext",
NULL
};
if (*input == '?' || !*input) {
r_core_cmd_help (core, help_msg_cp);
return false;
}
use_corefile = (*input == '.');
input++;
if (!*input) {
r_core_cmd_help (core, help_msg_cp);
return false;
}
if (use_corefile) {
char *file = r_core_cmd_str (core, "ij~{core.file}");
bool ret;
if (!file) {
return false;
}
r_str_trim (file);
if (!r_file_exists (file)) {
R_LOG_ERROR ("%s is not a file on the disk. Can't copy, see `wt?`", file);
free (file);
return false;
}
char *newfile = r_str_newf ("%s.%s", file, input);
if (!newfile) {
free (file);
return false;
}
ret = r_file_copy (file, newfile);
free (file);
free (newfile);
return ret;
}
char **files = r_str_argv (input, NULL);
if (files) {
bool ret = false;
if (files[0] && files[1]) {
ret = r_file_copy (files[0], files[1]);
} else {
r_core_cmd_help (core, help_msg_cp);
}
r_str_argv_free (files);
return ret;
}
return false;
}
/* Show the bits for the bytes at addr and offset.
* If scr.color is enabled, when bytes differ 1 is colored graph_true and 0 is
* colored graph_false.
*/
static int cmp_bits(RCore *core, ut64 addr) {
RConsPrintablePalette *pal = &r_cons_singleton ()->context->pal;
const bool use_color = r_config_get_b (core->config, "scr.color");
const char *color_end = use_color? Color_RESET: "";
int i;
ut8 a, b;
bool a_bits[8], b_bits[8];
const char *a_colors[8], *b_colors[8];
r_io_nread_at (core->io, core->offset, &a, 1);
r_io_nread_at (core->io, addr, &b, 1);
/* Print offset header if enabled */
if (r_config_get_i (core->config, "hex.header")) {
const char *color = use_color? pal->offset: "";
char *n = r_str_newf ("0x%08" PFMT64x, core->offset);
const char *padding = r_str_pad (' ', strlen (n) - 10);
free (n);
r_cons_printf ("%s- offset -%s 7 6 5 4 3 2 1 0%s\n", color, padding, color_end);
}
/* Set up bits and colors */
for (i = 7; i >= 0; i--) {
a_bits[i] = a & (1 << i);
b_bits[i] = b & (1 << i);
if (use_color && a_bits[i] != b_bits[i]) {
a_colors[i] = a_bits[i]? pal->graph_true: pal->graph_false;
b_colors[i] = b_bits[i]? pal->graph_true: pal->graph_false;
} else {
a_colors[i] = "";
b_colors[i] = "";
}
}
r_cons_printf ("%s0x%08" PFMT64x "%s ", use_color? pal->graph_false: "", core->offset, color_end);
for (i = 7; i >= 0; i--) {
r_cons_printf ("%s%d%s%s", a_colors[i], a_bits[i], color_end, i? " ": "");
}
r_cons_printf ("\n%s0x%08" PFMT64x "%s ", use_color? pal->graph_true: "", addr, color_end);
for (i = 7; i >= 0; i--) {
r_cons_printf ("%s%d%s%s", b_colors[i], b_bits[i], color_end, i? " ": "");
}
r_cons_newline ();
// 0 if equal, 1 if not equal
// same return pattern as ?==
return a != b;
}
static const RList *symbols_of(RCore *core, int id0) {
RBinFile *bf = r_bin_file_find_by_id (core->bin, id0);
RBinFile *old_bf = core->bin->cur;
r_bin_file_set_cur_binfile (core->bin, bf);
const RList *list = bf? r_bin_get_symbols (core->bin): NULL;
r_bin_file_set_cur_binfile (core->bin, old_bf);
return list;
}
static const RList *imports_of(RCore *core, int id0) {
RBinFile *bf = r_bin_file_find_by_id (core->bin, id0);
RBinFile *old_bf = core->bin->cur;
r_bin_file_set_cur_binfile (core->bin, bf);
const RList *list = bf? r_bin_get_imports (core->bin): NULL;
r_bin_file_set_cur_binfile (core->bin, old_bf);
return list;
}
static const RList *libs_of(RCore *core, int id0) {
RBinFile *bf = r_bin_file_find_by_id (core->bin, id0);
RBinFile *old_bf = core->bin->cur;
r_bin_file_set_cur_binfile (core->bin, bf);
const RList *list = bf? r_bin_get_libs (core->bin): NULL;
r_bin_file_set_cur_binfile (core->bin, old_bf);
return list;
}
static void _core_cmp_info_libs(RCore *core, int id0, int id1) {
const RList *s0 = libs_of (core, id0);
const RList *s1 = libs_of (core, id1);
if (!s0 || !s1) {
R_LOG_ERROR ("Missing bin object");
return;
}
RListIter *iter, *iter2;
char *s, *s2;
if (id0 == id1) {
eprintf ("%d == %d\n", id0, id1);
return;
}
r_list_foreach (s0, iter, s) {
bool found = false;
r_list_foreach (s1, iter2, s2) {
if (!strcmp (s, s2)) {
found = true;
}
}
r_cons_printf ("%s%s\n", found? " ": "-", s);
}
r_list_foreach (s1, iter, s) {
bool found = false;
r_list_foreach (s0, iter2, s2) {
if (!strcmp (s, s2)) {
found = true;
}
}
if (!found) {
r_cons_printf ("+%s\n", s);
}
}
// r_list_free (s0);
// r_list_free (s1);
}
static void _core_cmp_info_imports(RCore *core, int id0, int id1) {
const RList *s0 = imports_of (core, id0);
const RList *s1 = imports_of (core, id1);
if (!s0 || !s1) {
R_LOG_ERROR ("Missing bin object");
return;
}
RListIter *iter, *iter2;
RBinImport *s, *s2;
if (id0 == id1) {
eprintf ("%d == %d\n", id0, id1);
return;
}
r_list_foreach (s0, iter, s) {
bool found = false;
r_list_foreach (s1, iter2, s2) {
if (!strcmp (s->name, s2->name)) {
found = true;
}
}
r_cons_printf ("%s%s\n", found? " ": "-", s->name);
}
r_list_foreach (s1, iter, s) {
bool found = false;
r_list_foreach (s0, iter2, s2) {
if (!strcmp (s->name, s2->name)) {
found = true;
}
}
if (!found) {
r_cons_printf ("+%s\n", s->name);
}
}
// r_list_free (s0);
// r_list_free (s1);
}
static void _core_cmp_info_symbols(RCore *core, int id0, int id1) {
const RList *s0 = symbols_of (core, id0);
const RList *s1 = symbols_of (core, id1);
if (!s0 || !s1) {
R_LOG_ERROR ("Missing bin object");
return;
}
RListIter *iter, *iter2;
RBinSymbol *s, *s2;
if (id0 == id1) {
eprintf ("%d == %d\n", id0, id1);
return;
}
r_list_foreach (s0, iter, s) {
bool found = false;
r_list_foreach (s1, iter2, s2) {
if (!strcmp (s->name, s2->name)) {
found = true;
}
}
r_cons_printf ("%s%s\n", found? " ": "-", s->name);
}
r_list_foreach (s1, iter, s) {
bool found = false;
r_list_foreach (s0, iter2, s2) {
if (!strcmp (s->name, s2->name)) {
found = true;
}
}
if (!found) {
r_cons_printf ("+%s\n", s->name);
}
}
}
static void _core_cmp_info(RCore *core, const char *input) {
RBinFile *cur = core->bin->cur;
int id0 = (cur && cur->o) ? cur->id: 0;
int id1 = atoi (input + 1);
// do the magic
switch (input[0]) {
case 's':
_core_cmp_info_symbols (core, id0, id1);
break;
case 'l':
_core_cmp_info_libs (core, id0, id1);
break;
case 'i':
_core_cmp_info_imports (core, id0, id1);
break;
default:
r_core_cmd_help (core, help_message_ci);
break;
}
}
static void cmd_curl(RCore *core, const char *arg) {
if (r_sys_getenv_asbool ("R2_CURL")) {
r_sys_cmdf ("curl %s", arg);
} else {
if (strstr (arg, "http") && strstr (arg, "://")) {
int len;
char *s = r_socket_http_get (arg, NULL, &len);
if (s) {
r_cons_write (s, len);
free (s);
}
} else {
eprintf ("Usage: curl [http-url]\n");
}
}
}
static char *myslurp(RCore *core, const char *a, ut64 *sz) {
if (*a == '$') {
RCmdAliasVal *v = r_cmd_alias_get (core->rcmd, a + 1);
if (v) {
char *v_str = r_cmd_alias_val_strdup (v);
*sz = strlen (v_str);
return v_str;
}
} else {
size_t ssz;
char *rc = r_file_slurp (a, &ssz);
*sz = ssz;
return rc;
}
*sz = 0;
return NULL;
}
static int cmd_cmp_posix(RCore *core, const char *a, const char *b) {
ut64 sa, sb;
char *ba = myslurp (core, a, &sa);
char *bb = myslurp (core, b, &sb);
int res = 0;
if (!ba || !bb) {
R_LOG_ERROR ("One or more files can't be read");
res = 1;
} else {
if (sa == sb) {
res = memcmp (ba, bb, sa)? 1: 0;
} else {
res = 1;
}
}
free (ba);
free (bb);
// return 0 if both files are the same
return res;
}
static int cmd_cmp(void *data, const char *input) {
static R_TH_LOCAL char *oldcwd = NULL;
int ret = 0, i, mode = 0;
RCore *core = (RCore *)data;
ut64 val = UT64_MAX;
char *filled;
ut8 *buf;
utAny wordcmp;
FILE *fd;
const ut8* block = core->block;
switch (*input) {
case 'p': // "cp"
return cmd_cp (data, input + 1);
break;
case 'a': // "ca"
if (input[1] == 't') { // "cat"
const char *path = r_str_trim_head_ro (input + 2);
if (*path == '$' && !path[1]) {
R_LOG_ERROR ("No alias name given");
} else if (*path == '$') {
RCmdAliasVal *v = r_cmd_alias_get (core->rcmd, path + 1);
if (v) {
char *v_str = r_cmd_alias_val_strdup (v);
r_cons_println (v_str);
free (v_str);
} else {
R_LOG_ERROR ("No such alias \"$%s\"", path + 1);
}
} else if (*path) {
if (r_fs_check (core->fs, path)) {
r_core_cmdf (core, "mg %s", path);
} else {
char *res = r_syscmd_cat (path);
if (res) {
r_cons_print (res);
free (res);
}
}
} else {
r_core_cmd_help_match (core, help_msg_c, "cat", true);
}
} else { // "ca"
r_core_cmd_help_match (core, help_msg_c, "cat", true);
}
break;
case 'w':
return cmd_cmp_watcher (core, input + 1);
break;
case '*': // c*"
if (!input[2]) {
eprintf ("Usage: cx* 00..22'\n");
return 0;
}
val = radare_compare (core, block, (ut8 *) input + 2,
strlen (input + 2) + 1, '*');
break;
case ' ': { // "c"
char *str = strdup (input + 1);
int len = r_str_unescape (str);
val = radare_compare (core, block, (ut8 *) str, len, 0);
free (str);
break;
}
case 'j': // "cj"
if (input[1] != ' ') {
eprintf ("Usage: cj [string]\n");
} else {
char *str = strdup (input + 2);
int len = r_str_unescape (str);
val = radare_compare (core, block, (ut8 *) str, len, 'j');
free (str);
}
break;
case 'x': // "cx"
switch (input[1]) {
case ' ':
mode = 0;
input += 2;
break;
case '*':
if (input[2] != ' ') {
eprintf ("Usage: cx* 00..22'\n");
return 0;
}
mode = '*';
input += 3;
break;
default:
eprintf ("Usage: cx 00..22'\n");
return 0;
}
if (!(filled = (char *) malloc (strlen (input) + 1))) {
return false;
}
memcpy (filled, input, strlen (input) + 1);
if (!(buf = (ut8 *) malloc (strlen (input) + 1))) {
free (filled);
return false;
}
ret = r_hex_bin2str (block, strlen (input) / 2, (char *) buf);
for (i = 0; i < ret * 2; i++) {
if (filled[i] == '.') {
filled[i] = buf[i];
}
}
ret = r_hex_str2bin (filled, buf);
if (ret < 1) {
R_LOG_ERROR ("Cannot parse hexpair");
} else {
val = radare_compare (core, block, buf, ret, mode);
}
free (buf);
free (filled);
break;
case 'X': // "cX"
buf = malloc (core->blocksize);
if (buf) {
if (!r_io_read_at (core->io, r_num_math (core->num,
input + 1), buf, core->blocksize)) {
R_LOG_ERROR ("Cannot read hexdump");
} else {
val = radare_compare (core, block, buf, core->blocksize, mode);
}
free (buf);
}
break;
case 'f': // "cf"
if (input[1] != ' ') {
R_LOG_INFO ("Please. use 'cf [file]'");
return false;
}
fd = r_sandbox_fopen (input + 2, "rb");
if (!fd) {
R_LOG_ERROR ("Cannot open file '%s'", input + 2);
return false;
}
buf = (ut8 *) malloc (core->blocksize);
if (buf) {
if (fread (buf, 1, core->blocksize, fd) < 1) {
R_LOG_ERROR ("Cannot read file %s", input + 2);
} else {
val = radare_compare (core, block, buf, core->blocksize, 0);
}
fclose (fd);
free (buf);
} else {
fclose (fd);
return false;
}
break;
case 'd': // "cd"
while (input[1] == ' ') input++;
if (input[1]) {
if (!strcmp (input + 1, "-")) {
if (oldcwd) {
char *newdir = oldcwd;
oldcwd = r_sys_getdir ();
if (r_sandbox_chdir (newdir) == -1) {
R_LOG_ERROR ("Cannot chdir to %s", newdir);
free (oldcwd);
oldcwd = newdir;
} else {
free (newdir);
}
} else {
// nothing to do here
}
} else if (input[1] == '~' && input[2] == '/') {
char *homepath = r_str_home (input + 3);
if (homepath) {
if (*homepath) {
free (oldcwd);
oldcwd = r_sys_getdir ();
if (r_sandbox_chdir (homepath) == -1) {
R_LOG_ERROR ("Cannot chdir to %s", homepath);
}
}
free (homepath);
} else {
R_LOG_ERROR ("Cannot find home");
}
} else {
free (oldcwd);
oldcwd = r_sys_getdir ();
if (r_sandbox_chdir (input + 1) == -1) {
R_LOG_ERROR ("Cannot chdir to %s", input + 1);
}
}
} else {
char *home = r_sys_getenv (R_SYS_HOME);
if (!home || r_sandbox_chdir (home) == -1) {
R_LOG_ERROR ("Cannot find home");
}
free (home);
}
break;
case '1':
case '2':
case '4':
case '8': {
const char width = *input++;
const char mode = *input == '*'? '*': 0;
const char *arg;
utAny cmp_val;
arg = *input? r_str_trim_head_ro (input + 1): NULL;
if (input[0] == '?' || R_STR_ISEMPTY (arg)) {
r_core_cmd_help_match_spec (core, help_msg_c, "c", width, true);
break;
}
if (width == '1') {
if (mode == '*') {
R_LOG_ERROR ("c1 does not support * mode");
r_core_cmd_help_match (core, help_msg_c, "c1", true);
} else {
val = cmp_bits (core, r_num_math (core->num, arg));
}
} else if (width == '2') {
cmp_val.v16 = (ut16) r_num_math (core->num, arg);
val = radare_compare (core, block, (ut8 *) &cmp_val.v16, sizeof (wordcmp.v16), mode);
} else if (width == '4') {
cmp_val.v32 = (ut32) r_num_math (core->num, arg);
val = radare_compare (core, block, (ut8 *) &cmp_val.v32, sizeof (wordcmp.v32), mode);
} else if (width == '8') {
cmp_val.v64 = r_num_math (core->num, arg);
val = radare_compare (core, block, (ut8 *) &cmp_val.v64, sizeof (wordcmp.v64), mode);
}
break;
}
case 'c': // "cc"
if (input[1] == '?') { // "cc?"
r_core_cmd0 (core, "c?~cc");
} else if (input[1] == 'd') { // "ccd"
if (input[2] == 'd') { // "ccdd"
cmd_cmp_disasm (core, input + 3, 'd');
} else {
cmd_cmp_disasm (core, input + 2, 'c');
}
} else {
ut32 oflags = core->print->flags;
ut64 addr = 0; // TOTHINK: Not sure what default address should be
if (input[1] == 'c') { // "ccc"
core->print->flags |= R_PRINT_FLAGS_DIFFOUT;
addr = r_num_math (core->num, input + 2);
} else {
if (*input && input[1]) {
addr = r_num_math (core->num, input + 2);
}
}
int col = core->cons->columns > 123;
ut8 *b = malloc (core->blocksize);
if (b) {
memset (b, 0xff, core->blocksize);
r_io_read_at (core->io, addr, b, core->blocksize);
r_print_hexdiff (core->print, core->offset, block,
addr, b, core->blocksize, col);
free (b);
}
core->print->flags = oflags;
}
break;
case 'i': // "ci"
_core_cmp_info (core, input + 1);
break;
case 'g': // "cg"
{ // XXX: this is broken
int diffops = 0;
RCore *core2;
char *file2 = NULL;
switch (input[1]) {
case 'o': // "cgo"
file2 = (char *) r_str_trim_head_ro (input + 2);
if (*file2) {
r_anal_diff_setup (core->anal, true, -1, -1);
} else {
eprintf ("Usage: cgo [file]\n");
return false;
}
break;
case 'f': // "cgf"
R_LOG_TODO ("agf is experimental");
r_anal_diff_setup (core->anal, true, -1, -1);
r_core_gdiff_fcn (core, core->offset,
r_num_math (core->num, input + 2));
return false;
case ' ':
file2 = (char *) r_str_trim_head_ro (input + 2);
r_anal_diff_setup (core->anal, false, -1, -1);
break;
default: {
const char *help_message[] = {
"Usage: cg", "", "Graph code commands",
"cg", "", "diff ratio among functions (columns: off-A, match-ratio, off-B)",
"cgf", "[fcn]", "compare functions (curseek vs fcn)",
"cgo", "", "opcode-bytes code graph diff",
NULL
};
r_core_cmd_help (core, help_message);
return false;
}
}
if (r_file_size (file2) <= 0) {
R_LOG_ERROR ("Cannot compare with file %s", file2);
return false;
}
if (!(core2 = r_core_new ())) {
R_LOG_ERROR ("Cannot init diff core");
return false;
}
r_core_loadlibs (core2, R_CORE_LOADLIBS_ALL, NULL);
core2->io->va = core->io->va;
if (!r_core_file_open (core2, file2, 0, 0LL)) {
R_LOG_ERROR ("Cannot open diff file '%s'", file2);
r_core_free (core2);
r_core_bind_cons (core);
return false;
}
// TODO: must replicate on core1 too
r_config_set_i (core2->config, "io.va", true);
r_anal_diff_setup (core->anal, diffops, -1, -1);
r_anal_diff_setup (core2->anal, diffops, -1, -1);
r_core_bin_load (core2, file2,
r_config_get_i (core->config, "bin.baddr"));
r_core_gdiff (core, core2);
r_core_diff_show (core, core2);
/* exchange a segfault with a memleak */
core2->config = NULL;
r_core_free (core2);
r_core_bind_cons (core);
}
break;
case 'u': // "cu"
if (r_str_startswith (input, "url")) {
const char *arg = r_str_trim_head_ro (input + 3);
cmd_curl (core, arg);
break;
}
switch (input[1]) {
case '.':
case ' ':
radare_compare_unified (core, core->offset,
r_num_math (core->num, input + 2),
core->blocksize);
break;
case '1':
case '2':
case '4':
case '8':
radare_compare_words (core, core->offset,
r_num_math (core->num, input + 2),
core->blocksize, input[1] - '0');
break;
case 'd':
cmd_cmp_disasm (core, input + 2, 'u');
break;
default: {
const char *help_msg[] = {
"Usage: cu", " [offset]", "# Prints unified comparison to make hexpatches",
"cu", " $$+1 > p", "compare hexpairs from current seek and +1",
"cu1", " $$+1 > p", "compare bytes from current seek and +1",
"cu2", " $$+1 > p", "compare words (half, 16bit) from current seek and +1",
"cu4", " $$+1 > p", "compare dwords from current seek and +1",
"cu8", " $$+1 > p", "compare qwords from current seek and +1",
"cud", " $$+1 > p", "compare disasm current seek and +1",
"wu", " p", "apply unified hex patch (see output of cu)",
NULL
};
r_core_cmd_help (core, help_msg);
}
}
break;
case '?':
r_core_cmd_help (core, help_msg_c);
break;
case 'v': { // "cv"
int sz = input[1];
if (sz == ' ') {
switch (r_config_get_i (core->config, "asm.bits")) {
case 8: sz = '1'; break;
case 16: sz = '2'; break;
case 32: sz = '4'; break;
case 64: sz = '8'; break;
default: sz = '4'; break; // default
}
}
// TODO: honor endian
switch (sz) {
case '1': { // "cv1"
ut8 n = (ut8) r_num_math (core->num, input + 2);
if (block[0] == n) {
r_cons_printf ("0x%08"PFMT64x "\n", core->offset);
r_core_return_value (core, 0);
} else {
r_core_return_value (core, 1);
}
break;
}
case '2': { // "cv2"
ut16 n = (ut16) r_num_math (core->num, input + 2);
if (core->blocksize >= 2 && *(ut16*)block == n) {
r_cons_printf ("0x%08"PFMT64x "\n", core->offset);
r_core_return_value (core, 0);
} else {
r_core_return_value (core, 1);
}
break;
}
case '4': { // "cv4"
ut32 n = (ut32) r_num_math (core->num, input + 2);
if (core->blocksize >= 4 && *(ut32*)block == n) {
r_cons_printf ("0x%08"PFMT64x "\n", core->offset);
r_core_return_value (core, 0);
} else {
r_core_return_value (core, 1);
}
break;
}
case '8': { // "cv8"
ut64 n = (ut64) r_num_math (core->num, input + 2);
if (core->blocksize >= 8 && *(ut64*)block == n) {
r_cons_printf ("0x%08"PFMT64x "\n", core->offset);
r_core_return_value (core, 0);
} else {
r_core_return_value (core, 1);
}
break;
}
default:
r_core_return_value (core, 1);
// fallthrough
case '?':
eprintf ("Usage: cv[1248] [num]\n"
"Show offset if current value equals to the one specified\n"
" /v 18312 # serch for a known value\n"
" dc\n"
" cv4 18312 @@ hit*\n"
" dc\n");
break;
}
}
break;
case 'V': { // "cV"
int sz = input[1];
if (sz == ' ') {
switch (r_config_get_i (core->config, "asm.bits")) {
case 8: sz = '1'; break;
case 16: sz = '2'; break;
case 32: sz = '4'; break;
case 64: sz = '8'; break;
default: sz = '4'; break; // default
}
} else if (sz == '?') {
eprintf ("Usage: cV[1248] [addr] @ addr2\n"
"compare n bytes from one address to current one and return in $? 0 or 1\n");
}
sz -= '0';
if (sz > 0) {
ut64 at = r_num_math (core->num, input + 2);
ut8 buf[8] = {0};
if (r_io_read_at (core->io, at, buf, sizeof (buf)) < 1) {
r_core_return_value (core, -1);
break;
}
int val = memcmp (buf, core->block, R_MIN (core->blocksize, sz))? 1: 0;
r_core_return_value (core, val);
}
break;
}
case 'l': // "cl"
if (strchr (input, 'f')) {
r_cons_flush ();
} else if (input[1] == 0) {
r_cons_fill_line ();
// r_cons_clear_line (0);
} else if (!strchr (input, '0')) {
r_cons_clear00 ();
}
break;
case 'm': // "cmp"
if (input[1] != 'p' || strchr (input, '?')) {
r_core_cmd_help (core, help_msg_cmp);
} else {
int argc;
char **argv = r_str_argv (r_str_trim_head_ro (input + 2), &argc);
if (argc == 2) {
int res = cmd_cmp_posix (core, argv[0], argv[1]);
r_core_return_value (core, res);
} else {
r_core_cmd_help (core, help_msg_cmp);
}
free (argv);
}
break;
default:
r_core_cmd_help (core, help_msg_c);
break;
}
if (val != UT64_MAX) {
r_core_return_value (core, val);
}
return 0;
}