Tracing and Coverage - measures and countermeasures
One of the most useful tools for the reverse engineer (and forward engineer :P) is the program execution tracer. This yields either program coverage (which lines have been executed) or a full trace (list of executed instructions in order). Qira
is a very good example, leveraging either qemu
or PIN
to take an execution trace and display it in a nice GUI. But many other tracers exist, including DynamoRIO drcov,
Frida
and Intel PIN based tools. Finally, there are whole-system tracers, such as PANDA. I want to compare some of these, point out some shortcomings and illustrate some countermeasures.
In a previous post on ptrace
, I showed some simple programs that will detect a debugger using ptrace
. Let's start with those and then explore tougher cookies....
Super simple ptrace check
#include <stdio.h>
#include <sys/ptrace.h>
int main() {
if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) {
printf("Debugger\n");
} else {
printf("Normal\n");
}
return 0;
}
qira
$ qira ./superSimplePtrace
Debugger
qira using PIN
$ qira --pin ./superSimplePtrace
Normal
drcov
$ bin64/drrun -t drcov -- ./superSimplePtrace
frida
- no need yet, test in more difficult case below
PANDA
- no need yet, test in more difficult case below
Less simple ptrace check - multiple ptrace calls in single thread
#include <stdio.h>
#include <sys/ptrace.h>
int main()
{
if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) // first call
{
printf("Debugger (first check)\n");
}
else
{
if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) // second call
{
printf("Normal\n");
}
else
{
printf("Debugger (second check)\n");
}
}
return 0;
}
qira
$ qira ./lessSimplePtrace
Debugger (first check)
qira using PIN
$ qira --pin ./lessSimplePtrace
Normal
drcov
$ bin64/drrun -t drcov -- ./lessSimplePtrace
Normal
frida
- no need yet, test in more difficult case below
PANDA
- no need yet, test in more difficult case below
Self-debugging ptrace
This code is adapted from [5]
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <stdio.h>
int main()
{
pid_t child;
int status;
switch((child = fork()))
{
case 0:
ptrace(PTRACE_TRACEME);
// this is the actual 'useful' program code should run for a while - simulate with delay
usleep(1000000);
printf("no debugger detected!");
return 2;
case -1:
perror("fork");
return 1;
default:
if (ptrace(PTRACE_ATTACH, child))
{
kill(child, SIGKILL);
printf("ptrace_attach failed - debugger?");
return 3;
}
while (waitpid(child, &status,0) != -1)
ptrace(PTRACE_CONT,child,0,0);
return 0;
}
return 0;
}
qira
$ qira ./selfdebug
ptrace_attach failed - debugger?
qira using PIN
$ qira --pin ./selfdebug
no debugger detected!
drcov
$ bin64/drrun -t drcov -- ./selfdebug
no debugger detected!
frida
- no need yet, test in more difficult case below
PANDA
- no need yet, test in more difficult case below
Nanomites
Next, I am running a binary challenge from root-me.org that contains nanomites [2],[3].
qira
This fails, even when the correct flag is passed.
$ qira ./ch28.bin
So you want to trace me?!
Wrong! try hard! :)
qira using PIN
This fails, even when the correct flag is passed. But in a different place than before.
$ qira --pin ./ch28.bin
Please input the flag:
Hummmmmmm NO WAY.
drcov
This fails, even when the correct flag is passed, same place as qira with PIN
$ bin64/drrun -t drcov -- ./ch28.bin
Please input the flag:
Hummmmmmm NO WAY.
frida
I am using a script by lighthouse [5] to collect coverage information using frida.
$ sudo python frida-drcov.py ch28.bin
[*] Attaching to pid '6171' on device 'local'...
[+] Attached. Loading script...
[+] Got module info.
Starting to stalk threads...
Stalking thread 6171.
Done stalking threads.
[*] Now collecting info, control-D to terminate....
and in a separate window
$ ./ch28.bin
Please input the flag:
Hummmmmmm NO WAY.
PANDA
details here: PANDA for code coverage with IDA pro
$ ./ch28.bin
Please input the flag:
POOOOOOOOOOOOOOOOOOOOOOOOO God damn!! You won!
Whole system tracing is the only method that can yield code coverage and execution tracing in this and many other cases...
References:
- [1] Advanced Techniques For Anti-Debugging - https://lib.ugent.be/fulltxt/RUG01/002/367/296/RUG01-002367296_2017_0001_AC.pdf
- [2] Taming a wild nanomite-protected MIPS binary with symbolic execution: No Such Crackme - https://doar-e.github.io/blog/2014/10/11/taiming-a-wild-nanomite-protected-mips-binary-with-symbolic-execution-no-such-crackme/
- [3] Nanomite and Debug Blocker for Linux Applications - https://www.codeproject.com/Articles/621236/Nanomite-and-Debug-Blocker-for-Linux-Applications
- [4] More Android Anti-Debugging Fun - https://www.vantagepoint.sg/blog/89-more-android-anti-debugging-fun
- [5] Lighthouse - Code Coverage Explorer for IDA Pro & Binary Ninja - https://github.com/gaasedelen/lighthouse
- [6] PANDA - Platform for Architecture-Neutral Dynamic Analysis - https://github.com/panda-re