-
Notifications
You must be signed in to change notification settings - Fork 481
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Andrew Fasano
committed
Oct 30, 2023
1 parent
6b676d0
commit 2acdb22
Showing
7 changed files
with
274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
CFLAGS+= -std=c++17 | ||
$(PLUGIN_TARGET_DIR)/panda_$(PLUGIN_NAME).so: \ | ||
$(PLUGIN_OBJ_DIR)/$(PLUGIN_NAME).o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
Plugin: CallWithArg | ||
=========== | ||
|
||
Summary | ||
------- | ||
|
||
At every function call, check the first N (potential) arguments to see if any are a specified integer value or a pointer to a specified string value. If so, trigger a custom PPP event. | ||
|
||
The goal here is to dynamically identify functions that take a known value as an argument. **Many false positives are to be expected!** But if you test multiple values, you can use this plugin to easily identify a hookable function in a target program to build custom introspection gadgets. | ||
|
||
Arguments | ||
--------- | ||
|
||
`targets`: an `_` seperated list of hex numbers and/or strings to check for (e.g., `0x1234_hello world_0xABCDEF`) | ||
`verbose`: If set to 1, print on every detected call. | ||
`N`: How many arguments to examine. Default 2. Only supports standard linux calling conventions for now. | ||
|
||
|
||
Dependencies | ||
------------ | ||
`callstack_instr` | ||
|
||
APIs and Callbacks | ||
------------------ | ||
API | ||
* `void add_target_string(char* s)`, `void add_target_num(target_ulong x)`: Add a new target | ||
* `bool remove_target_string(char* s)`, `bool remove_target_num(target_ulong x)`: Remove an existing target. Returns true if the argument was previously a target | ||
|
||
Callbacks: | ||
``` | ||
on_call_match_num(CPUState *cpu, target_ulong* args, uint matching_idx, uint args_read); | ||
``` | ||
|
||
``` | ||
on_call_match_str(CPUState *cpu, target_ulong* args, uint matching_idx, char* value, uint args_read); | ||
``` | ||
|
||
Example | ||
------- | ||
``` | ||
$(python3 -m pandare.qcows x86_64) -panda callwitharg:targets=root_hello_0x41414141,verbose=1 | ||
root@guest# echo hello | ||
root@guest# echo AAAA | ||
root@guest# whoami | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
#include "panda/plugin.h" | ||
#include "panda/plugin_plugin.h" | ||
#include <unordered_set> | ||
#include <string> | ||
#include <memory> | ||
|
||
#include "callwitharg.h" | ||
#include "callstack_instr/callstack_instr.h" | ||
|
||
extern "C" { | ||
#include "callwitharg_int_fns.h" | ||
bool init_plugin(void *); | ||
void uninit_plugin(void *); | ||
PPP_PROT_REG_CB(on_call_match_str); | ||
PPP_PROT_REG_CB(on_call_match_num); | ||
} | ||
|
||
PPP_CB_BOILERPLATE(on_call_match_str); | ||
PPP_CB_BOILERPLATE(on_call_match_num); | ||
|
||
uint N; | ||
bool verbose; | ||
void on_call_with_args(CPUState *cpu, target_ulong func_pc); | ||
|
||
// Sets of the targets we're looking for | ||
std::unordered_set<std::string> string_targets; | ||
std::unordered_set<target_ulong> int_targets; | ||
void add_target_string(char* target) { | ||
if (verbose) { | ||
printf("Adding string target %s\n", target); | ||
} | ||
std::string target_s = std::string(target); | ||
string_targets.insert(target_s); | ||
} | ||
|
||
bool remove_target_string(char* target) { | ||
if (verbose) { | ||
printf("Removing string target %s\n", target); | ||
} | ||
std::string target_s = std::string(target); | ||
return string_targets.erase(target_s) > 0; | ||
} | ||
|
||
void add_target_num(target_ulong target) { | ||
if (verbose) { | ||
printf("Adding number target " TARGET_FMT_lx "\n", target); | ||
} | ||
int_targets.insert(target); | ||
} | ||
|
||
bool remove_target_num(target_ulong target) { | ||
if (verbose) { | ||
printf("Removing number target " TARGET_FMT_lx "\n", target); | ||
} | ||
return int_targets.erase(target) > 0; | ||
} | ||
|
||
bool init_plugin(void *self) { | ||
std::unique_ptr<panda_arg_list, void(*)(panda_arg_list*)> args( | ||
panda_get_args("callwitharg"), panda_free_args); | ||
|
||
const char * target_str_const = panda_parse_string_opt(args.get(), "targets", "", "Hex values and strings to search for in arguments. Seperated by _s"); | ||
|
||
if (strlen(target_str_const) > 0) { | ||
char *target_str = strdup(target_str_const); // Make a mutable copy | ||
|
||
// First split string on _'s, then iterate over each target and | ||
// add to the appropriate set | ||
char* target = strtok(target_str, "_"); | ||
while (target != NULL) { | ||
// If target is a hex value, add it to the int_targets set | ||
if (target[0] == '0' && target[1] == 'x') { | ||
target_ulong target_num = strtoul(target, NULL, 16); | ||
add_target_num(target_num); | ||
} else { | ||
// Otherwise, add it to the string_targets set | ||
add_target_string(target); | ||
} | ||
target = strtok(NULL, "_"); | ||
} | ||
} | ||
|
||
verbose = panda_parse_bool_opt(args.get(), "verbose", "enable verbose output"); | ||
N = (uint)panda_parse_uint32_opt(args.get(), "N", 2, "Maximum number of arguments to examine on each call"); | ||
|
||
#if defined(TARGET_ARM) || defined(TARGET_MIPS) || defined(TARGET_X86_64) | ||
PPP_REG_CB("callstack_instr", on_call, on_call_with_args); | ||
return true; | ||
#else | ||
printf("ERROR: Unsupported architecture for targetcmp\n"); | ||
return false; | ||
#endif | ||
} | ||
|
||
typedef struct { | ||
target_ulong *args; | ||
size_t count; | ||
} Arguments; | ||
|
||
bool _get_args_for_arch(CPUArchState *env, Arguments *args, int N) { | ||
#ifdef TARGET_ARM | ||
for (int i = 0; i < N; ++i) { | ||
args->args[i] = env->regs[i]; | ||
} | ||
#elif defined(TARGET_MIPS) | ||
for (int i = 0; i < N; ++i) { | ||
args->args[i] = env->active_tc.gpr[4 + i]; | ||
} | ||
#elif defined(TARGET_X86_64) | ||
// Handle SysV ABI here, or make it a parameter to the function | ||
const int regs[] = {7, 6}; // RDI, RSI | ||
for (int i = 0; i < N; ++i) { | ||
args->args[i] = env->regs[regs[i]]; | ||
} | ||
#else | ||
return false; // Error | ||
#endif | ||
|
||
return true; // All good | ||
} | ||
|
||
bool get_n_args(CPUState *cpu, Arguments *args, uint n) { | ||
CPUArchState *UNUSED(env) = (CPUArchState *)cpu->env_ptr; | ||
args->args = (target_ulong*)malloc(n * sizeof(target_ulong)); | ||
args->count = N; | ||
return _get_args_for_arch(env, args, N); | ||
} | ||
|
||
|
||
// Called on every guest function call | ||
void on_call_with_args(CPUState *cpu, target_ulong func_pc) { | ||
|
||
// What kind of target do we have? | ||
bool have_strings = string_targets.size() > 0; | ||
bool have_nums = int_targets.size() > 0; | ||
|
||
size_t max_len = 0; | ||
|
||
if (!have_strings && !have_nums) return; | ||
|
||
if (have_strings) { | ||
for (auto it = string_targets.begin(); it != string_targets.end(); ++it) { | ||
if (it->length() > max_len) { | ||
max_len = it->length(); | ||
} | ||
} | ||
} | ||
|
||
Arguments args; | ||
if (!get_n_args(cpu, &args, N)) { | ||
// Error! | ||
printf("Failed to get %d args\n", N); | ||
return; | ||
} | ||
|
||
// Check the up to N arguments for matches | ||
for (uint i=0; i < N; i++) { | ||
// Is argument a string or a number? | ||
if (have_nums) { | ||
if (int_targets.find(args.args[i]) != int_targets.end()) { | ||
if (verbose) { | ||
printf("Found target " TARGET_FMT_lx " in call at " TARGET_FMT_lx ". In argument %d\n", args.args[i], func_pc, i); | ||
} | ||
PPP_RUN_CB(on_call_match_num, cpu, func_pc, args.args, i, N); | ||
} | ||
} | ||
|
||
if (have_strings) { | ||
// Read string from guest memory | ||
char str[max_len+1]; | ||
if (panda_virtual_memory_read(cpu, args.args[i], (uint8_t*)str, max_len) != 0) { | ||
// Read failed | ||
continue; | ||
} | ||
str[max_len] = '\0'; | ||
|
||
// Check if string is in set of targets | ||
std::string str_s = std::string(str); | ||
if (string_targets.find(str_s) != string_targets.end()) { | ||
if (verbose) { | ||
printf("Found string target %s in call at " TARGET_FMT_lx ". In argument %d\n", str, func_pc, i); | ||
} | ||
PPP_RUN_CB(on_call_match_str, cpu, func_pc, args.args, str, i, N); | ||
} | ||
} | ||
} | ||
free(args.args); | ||
} | ||
|
||
void uninit_plugin(void *self) { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#ifndef __CALLWITHARG_H | ||
#define __CALLWITHARG_H | ||
|
||
// BEGIN_PYPANDA_NEEDS_THIS -- do not delete this comment bc pypanda | ||
// api autogen needs it. And don't put any compiler directives | ||
// between this and END_PYPANDA_NEEDS_THIS except includes of other | ||
// files in this directory that contain subsections like this one. | ||
|
||
PPP_CB_TYPEDEF(void, on_call_match_num, CPUState *env, target_ulong func_addr, target_ulong *args, uint matching_idx, uint args_read); | ||
PPP_CB_TYPEDEF(void, on_call_match_str, CPUState *env, target_ulong func_addr, target_ulong *args, char* value, uint matching_idx, uint args_read); | ||
|
||
// END_PYPANDA_NEEDS_THIS -- do not delete this comment! | ||
|
||
#endif | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
typedef void target_ulong; | ||
typedef void bool; | ||
#include "callwitharg_int_fns.h" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#ifndef __CALLWITHARG_INT_FNS_H__ | ||
#define __CALLWITHARG_INT_FNS_H__ | ||
|
||
// BEGIN_PYPANDA_NEEDS_THIS -- do not delete this comment bc pypanda | ||
// api autogen needs it. And don't put any compiler directives | ||
// between this and END_PYPANDA_NEEDS_THIS except includes of other | ||
// files in this directory that contain subsections like this one. | ||
|
||
// Public interface | ||
void add_target_string(char* target); | ||
bool remove_target_string(char* target); | ||
void add_target_num(target_ulong target); | ||
bool remove_target_num(target_ulong target); | ||
// END_PYPANDA_NEEDS_THIS -- do not delete this comment! | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ asid_instr_count | |
asidstory | ||
callfunc | ||
callstack_instr | ||
callwitharg | ||
checkpoint | ||
collect_code | ||
correlatetaps | ||
|