Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for fuzzing by attaching to running processes on Windows. #38

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ The following command line arguments are supported:

`-dict <path>` - Provides a dictionary to be used during mutation. The dictionary should be a text file with every entry on a separate line. `\xXX` escape sequences can be used.

`-process-name <name of running process> - Enables fuzzing for processes that are already running in the operating system such as Windows services. The fuzzer will attach to the specified running process and get coverage from there. Then, the script specified after `--` will be opened in a new thread and will send the current testcase to running process. Note: this mode only allows for one thread and file delivery through the script.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure windows services is a good example here as afaik all windows services run under the same process name (svchost.exe)


For TinyInst instrumentation command line arguments, refer to [TinyInst readme](https://github.com/googleprojectzero/TinyInst).

Example (macOS):
Expand Down
30 changes: 25 additions & 5 deletions fuzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ void Fuzzer::ParseOptions(int argc, char **argv) {
incremental_coverage = GetBinaryOption("-incremental_coverage", argc, argv, true);

add_all_inputs = GetBinaryOption("-add_all_inputs", argc, argv, false);

process_name = GetOption("-process_name", argc, argv);
ifratric marked this conversation as resolved.
Show resolved Hide resolved

attach_mode = false;

if (process_name != NULL) {
attach_mode = true;
//set threads to one as multiple threads attaching to one process does not work well
num_threads = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps if num_threads is not 1 already it would be a good idea to notify the user that Jackalope is changing the thread count, e.g.
WARN("Using a single fuzzing thread when process_name is set");

}
}

void Fuzzer::SetupDirectories() {
Expand Down Expand Up @@ -245,8 +255,14 @@ RunResult Fuzzer::RunSampleAndGetCoverage(ThreadContext *tc, Sample *sample, Cov
FATAL("Repeatedly failed to deliver sample");
}
}
RunResult result;
if (attach_mode) {
script = ArgvToCmd(tc->target_argc, tc->target_argv);
result = tc->instrumentation->Attach(script, process_name, init_timeout, timeout);
} else {
result = tc->instrumentation->Run(tc->target_argc, tc->target_argv, init_timeout, timeout);
}

RunResult result = tc->instrumentation->Run(tc->target_argc, tc->target_argv, init_timeout, timeout);
tc->instrumentation->GetCoverage(*coverage, true);

// save crashes and hangs immediately when they are detected
Expand Down Expand Up @@ -325,8 +341,12 @@ RunResult Fuzzer::TryReproduceCrash(ThreadContext* tc, Sample* sample, uint32_t
FATAL("Repeatedly failed to deliver sample");
}
}

result = tc->instrumentation->RunWithCrashAnalysis(tc->target_argc, tc->target_argv, init_timeout, timeout);
if (attach_mode) {
script = ArgvToCmd(tc->target_argc, tc->target_argv);
result = tc->instrumentation->AttachWithCrashAnalysis(script, process_name, init_timeout, timeout);
} else {
result = tc->instrumentation->RunWithCrashAnalysis(tc->target_argc, tc->target_argv, init_timeout, timeout);
}
tc->instrumentation->ClearCoverage();

if (result == CRASH) return result;
Expand Down Expand Up @@ -409,8 +429,8 @@ RunResult Fuzzer::RunSample(ThreadContext *tc, Sample *sample, int *has_new_cove
if(new_thread_coverage.empty()) return result;
}

// printf("found new coverage: \n");
// PrintCoverage(initialCoverage);
//printf("found new coverage: \n");
//PrintCoverage(initialCoverage);

// the sample returned new coverage

Expand Down
4 changes: 4 additions & 0 deletions fuzzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ class Fuzzer {
uint64_t num_samples_discarded;
uint64_t num_threads;
uint64_t total_execs;

bool attach_mode;
char * script;
char * process_name;

void SaveState(ThreadContext *tc);
void RestoreState(ThreadContext *tc);
Expand Down
6 changes: 6 additions & 0 deletions instrumentation.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class Instrumentation {
return Run(argc, argv, init_timeout, timeout);
}

virtual RunResult Attach(char * script, char * process_name, uint32_t init_timeout, uint32_t timeout) = 0;

virtual RunResult AttachWithCrashAnalysis(char * script, char * process_name, uint32_t init_timeout, uint32_t timeout) {
return Attach(script, process_name, init_timeout, timeout);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary changes to spacing

virtual void CleanTarget() = 0;

virtual bool HasNewCoverage() = 0;
Expand Down
112 changes: 111 additions & 1 deletion tinyinstinstrumentation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ limitations under the License.

#include <sstream>


void TinyInstInstrumentation::Init(int argc, char **argv) {
instrumentation = new LiteCov();
instrumentation->Init(argc, argv);
Expand Down Expand Up @@ -136,6 +135,117 @@ RunResult TinyInstInstrumentation::RunWithCrashAnalysis(int argc, char** argv, u
return ret;
}

RunResult TinyInstInstrumentation::Attach(char * script, char * process_name, uint32_t init_timeout, uint32_t timeout) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function duplicates a lot of code from TinyInstInstrumentation::Run. Would be better if this was implemented by changing TinyInstInstrumentation::Run where appropriate. Probably the same thing with TinyInstInstrumentation::AttachWithCrashAnalysis and TinyInstInstrumentation::RunWithCrashAnalysis.

DebuggerStatus status;
RunResult ret = OTHER_ERROR;

if (instrumentation->IsTargetFunctionDefined()) {
if (cur_iteration == num_iterations) {
instrumentation->Kill();
cur_iteration = 0;
}
}

// else clear only when the target function is reached
if (!instrumentation->IsTargetFunctionDefined()) {
instrumentation->ClearCoverage();
}

uint32_t timeout1 = timeout;
if (instrumentation->IsTargetFunctionDefined()) {
timeout1 = init_timeout;
}

if (instrumentation->IsTargetAlive() && persist) {
status = instrumentation->Continue(timeout1);
} else {
instrumentation->Kill();
cur_iteration = 0;
instrumentation->script = script;
Sleep(init_timeout);
processID = FindProcessId(process_name);
status = instrumentation->Attach(processID, timeout1);
}

// if target function is defined,
// we should wait until it is hit
if (instrumentation->IsTargetFunctionDefined()) {
if (status != DEBUGGER_TARGET_START) {
// try again with a clean process
WARN("Target function not reached, retrying with a clean process\n");
instrumentation->Kill();
cur_iteration = 0;
instrumentation->script = script;
Sleep(init_timeout);
processID = FindProcessId(process_name);
status = instrumentation->Attach(processID, timeout1);
}

if (status != DEBUGGER_TARGET_START) {
switch (status) {
case DEBUGGER_CRASHED:
FATAL("Process crashed before reaching the target method\n");
break;
case DEBUGGER_HANGED:
FATAL("Process hanged before reaching the target method\n");
break;
case DEBUGGER_PROCESS_EXIT:
FATAL("Process exited before reaching the target method\n");
break;
default:
FATAL("An unknown problem occured before reaching the target method\n");
break;
}
}

instrumentation->ClearCoverage();

status = instrumentation->Continue(timeout);
}

switch (status) {
case DEBUGGER_CRASHED:
ret = CRASH;
instrumentation->Kill();
break;
case DEBUGGER_HANGED:
ret = HANG;
instrumentation->Kill();
break;
case DEBUGGER_PROCESS_EXIT:
ret = OK;
if (instrumentation->IsTargetFunctionDefined()) {
WARN("Process exit during target function\n");
ret = HANG;
}
break;
case DEBUGGER_TARGET_END:
if (instrumentation->IsTargetFunctionDefined()) {
ret = OK;
cur_iteration++;
} else {
FATAL("Unexpected status received from the debugger\n");
}
break;
default:
FATAL("Unexpected status received from the debugger\n");
break;
}

return ret;
}

RunResult TinyInstInstrumentation::AttachWithCrashAnalysis(char * script, char * process_name, uint32_t init_timeout, uint32_t timeout) {
// clean process when reproducing crashes
instrumentation->Kill();
// disable instrumentation when reproducing crashes
instrumentation->DisableInstrumentation();
RunResult ret = Attach(script, process_name, init_timeout, timeout);
instrumentation->Kill();
instrumentation->EnableInstrumentation();
return ret;
}

void TinyInstInstrumentation::CleanTarget() {
instrumentation->Kill();
}
Expand Down
4 changes: 4 additions & 0 deletions tinyinstinstrumentation.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class TinyInstInstrumentation : public Instrumentation {
RunResult Run(int argc, char** argv, uint32_t init_timeout, uint32_t timeout) override;
RunResult RunWithCrashAnalysis(int argc, char** argv, uint32_t init_timeout, uint32_t timeout) override;

RunResult TinyInstInstrumentation::Attach(char * script, char * process_name, uint32_t init_timeout, uint32_t timeout) override;
RunResult TinyInstInstrumentation::AttachWithCrashAnalysis(char * script, char * process_name, uint32_t init_timeout, uint32_t timeout) override;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary changes to spacing.

void CleanTarget() override;

bool HasNewCoverage() override;
Expand All @@ -47,6 +50,7 @@ class TinyInstInstrumentation : public Instrumentation {
protected:
LiteCov * instrumentation;
bool persist;
int processID;
int num_iterations;
int cur_iteration;
};
Expand Down