Aegis
is a library that allows you detect if your software is being debugged or not on Linux
, FreeBSD
, NetBSD
, OpenBSD
and
Windows
. You can use it natively from C
or use the Go
bind.
The name is about a lousy acronym: An ELF's -g inspection signalling. If you are hooked on
Greek mythology you should know that Aegis
is the name of the shield gave by Athena
to Perseus
to help him
kill Medusa
. If you are using it from Windows
understand as An Executable's -g inspection
signalling ;)
On Windows
we have plenty of ways to easily do this kind of detection. Opposingly, on Unix
world we do not have any
standard way. Aegis
is an attempt of filling up this gap.
You can use Aegis
as an anti-debugging mitigation
or as a debugging facility
. It just depends on you and your
current requirements.
Originally, I wrote this library to use on another tool of mine called blackcat
as an anti-debugging
stuff.
I am using a build tool of mine called Hefesto
(Yes, mythology, I love it).
If you are looking for running the build in all its capabilities you need Hefesto
otherwise I also supply a well-simple
Makefile.
The easiest way is:
black-beard@QueensAnneRevenge:~/src# git clone https://github.com/rafael-santiago/aegis --recursive
black-beard@QueensAnneRevenge:~/src/aegis/src# _
After following all steps to put Hefesto
to work on your system, just change to src
sub-directory:
black-beard@QueensAnneRevenge:~/src/aegis# cd src
black-beard@QueensAnneRevenge:~/src/aegis/src# _
The hardest part: invoke Hefesto
. Look:
black-beard@QueensAnneRevenge:~/src/aegis/src# hefesto
(...)
black-beard@QueensAnneRevenge:~/src/aegis/src# _
If all has occurred fine during your build, aegis
library was built at ../lib
sub-directory. Additionaly,
test has ran and all samples was built at ../samples
sub-directory.
In order to skip tests you must invoke Hefesto
with the option --no-tests
:
black-beard@QueensAnneRevenge:~/src/aegis/src# hefesto --no-tests
(...)
black-beard@QueensAnneRevenge:~/src/aegis/src# _
Well this will just build the library at ../lib
. The clumsy idea here is: If all has compiled so all is working...
Change to src
sub-directory:
black-beard@QueensAnneRevenge:~/src/aegis# cd src
black-beard@QueensAnneRevenge:~/src/aegis/src# _
Now it is just about calling make
:
black-beard@QueensAnneRevenge:~/src/aegis/src# make
(...)
black-beard@QueensAnneRevenge:~/src/aegis/src# _
If you are on some BSD-like
, besides make
you also need gmake
to run this limited alternative build.
On *BSD
you can also invoke the poor man's build
by running gmake
.
You can easily do it by invoking Hefesto
passing the build option --mkdist
:
black-beard@QueensAnneRevenge:~/src/aegis/src# hefesto --mkdist
black-beard@QueensAnneRevenge:~/src/aegis/src# _
Once done the distribution package will be at ../lib
sub-directory. The package's name depends on your system.
It will follow this nomenclature scheme: libaegis-<os-name>.zip
.
Go
is a language with automagically build capabilities. Once inside package sub-directory (gopkg/vN
) call
go build -a
or go test
will do the job. For samples, once inside a sample directory run go build -a -o sample-name
.
It would be the poor man's build for Go
.
If you are in a rush and looking for a more automated way of doing it. Inside src
top-level sub-directory invoke Hefesto
passing --gopkg
build option:
black-beard@QueensAnneRevenge:~/src/aegis/src# hefesto --gopkg
(...)
black-beard@QueensAnneRevenge:~/src/aegis/src# _
After a successful build you will got the Go
samples inside ../samples
with their names prepended with golang-
.
Aegis
is a well-simple tiny library:
-
It only has one header called
aegis.h
. -
It only has one library archive called
libaegis.a
.
Aegis
brings you two features:
-
Detect debugging.
-
Protect against debugging by using our simpathetic
Gorgon
(yes, I know, Greek mythology again).
Remark: In order to make this anti-debug resilient against library hooking you always should link your software static
.
Moreover, if you want to keep eavesdroppers out as much as possible: link your software static. Otherwise there is no necessity
of worrying about none of it. Because your front door is wide open or you do not have even a door! Haha!
In some bug hunting cases is useful to wait for debugger before continuing the program execution. Specially concurrent stuff or
even event oriented processing. In this case you can use aegis_has_debugger()
function.
This function returns 1 when a debugger has being attached otherwise 0.
The following program will wait for debugger before exiting:
#include <aegis.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigint_watchdog(int signo) {
printf("\nCanceled.\n");
exit(1);
}
int main(int argc, char **argv) {
signal(SIGINT, sigint_watchdog);
signal(SIGTERM, sigint_watchdog);
printf("*** Waiting for debug attachment (pid=%d)...\n", getpid());
while (!aegis_has_debugger()) {
usleep(1);
}
printf("*** Debugger is attached.\n");
return 0;
}
The program above can be found at src/samples
sub-directory under the name wait4debug.c
.
The manual compilation of this code is fairly simple and involves:
- To indicate where
aegis.h
is found. - To indicate where
libaegis.a
is found. - To pass
-laegis
flag to linker. - To pass
-lpthread
flag if you are onLinux
,FreeBSD
,NetBSD
orOpenBSD
.
All in one compilation line:
black-beard@QueensAnneRevenge:~/src/aegis/src# cd samples
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# gcc -I.. -L../../lib \
> wait4debug.c -owait4debug -laegis
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# _
On a terminal run wait4debug
:
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# ./wait4debug
*** Wait for debug attachment (pid=29670)
wait4debug
has facilitate the things for you by giving its pid
. Now on another terminal run GDB
as follows:
black-beard@QueensAnneRevenge:~# gdb attach 29670
(...)
(gdb)
You will attach to the wait4debug
process, now let's continue on GDB
:
black-beard@QueensAnneRevenge:~# gdb attach 29670
(...)
(gdb) continue
Continuing.
[Inferior 1 (process 29670) exited normally]
(gdb)
Nice, the program has exited. If you back to your wait4debug
terminal you will see something like:
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# ./wait4debug
*** Wait for debug attachment (pid=29670)
*** Debugger is attached.
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# _
Certain programs require some debugging avoidance. Aegis
features a nice and straightforward way to implement this kind
of mitigation. For doing that you need:
- To implement a exit checking function with the prototype:
int(void *)
. A return different from zero means that gorgon should exit. - If necessary to implement a on debugger function with the prototype:
void(void *)
. This function will be called when a debugger is detected. - To call
aegis_set_gorgon()
passing your exit checking function and its argument pointer, besides on debugger function and its argument pointer.
Take a look at the following code to get more details about:
/*
* Copyright (c) 2020, Rafael Santiago
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <aegis.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
int bye = 0;
void sigint_watchdog(int signo) {
bye = 1;
}
int disable_gorgon(void *args) {
return (*(int *)args);
}
void on_debugger(void *args) {
fprintf(stdout, "\ninfo: debugger detected.\n");
exit(1);
}
int main(int argc, char **argv) {
signal(SIGINT, sigint_watchdog);
signal(SIGTERM, sigint_watchdog);
if (aegis_set_gorgon(disable_gorgon, &bye, on_debugger, NULL) != 0) {
fprintf(stderr, "error: unable to set gorgon.\n");
exit(1);
}
fprintf(stdout, "info: process started (pid=%d)...\n", getpid());
while (!bye) {
usleep(2);
}
fprintf(stdout, "\ninfo: gracefully exiting, no debugger was detected.\n");
return 0;
}
You can find the presented code into src/samples/setgorgon.c
.
When on debugger function was passed as NULL
. It asks aegis
to use its default on debugger function. This
default callback is only about calling exit(1)
. Thus, after a debugger detection, the process will immediately exit.
There are some cases that you need to do something before exiting, for those cases on debugger function would be handy.
Anyway, once a debugger attached, the best action is terminate the process as soon as possible or try to kill the debugger.
On a terminal run setgorgon
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# ./setgorgon
info: process started (pid=28582)...
Now let's only press ctrl + c
:
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# ./setgorgon
info: process started (pid=28582)...
^C
info: gracefully exiting, no debugger was detected.
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# _
Nice but what about give debugging a try? Let's run it again:
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# ./setgorgon
info: process started (pid=14753)...
On another terminal let's attach GDB
:
black-beard@QueensAnneRevenge:~# gdb attach 14753
(...)
(gdb) _
Now, still on GDB
continue the setgorgon
process:
black-beard@QueensAnneRevenge:~# gdb attach 14753
(...)
(gdb) continue
Continuing.
[Thread 0x7f47880ec700 (LWP 14754) exited]
[Inferior 1 (process 14753) exited with code 01]
(gdb) _
No chance for our debugging attempt, let's go back to our setgorgon's terminal:
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# ./setgorgon
info: process started (pid=14753)...
info: debugger detected.
black-beard@QueensAnneRevenge:~/src/aegis/src/samples# _
No gracefully exiting message, it was really aborted to avoid debugging. Our gorgon has done her job.
Maybe you are asking why call this feature of Gorgon
. Well, Perseus
myth tells that Athena
gave him a shield (aegis)
for help him to kill Medusa
(also known as Gorgon
).Perseus
has killed her using aegis
. By watching for her
reflection in the shield he used the sword (also given by Athena
) to chop off Medusa
's head. After that Athena
has
picked aegis
back and in memory of Medusa
, Athena
put Medusa
's head in this shield.
Ancient greeks had used to sculpt or even drawn Gorgon
heads at the front door of their houses in order to scare enemies,
bad people, bad things and stuff. Maybe it could be the origin of the Medusa
's myth, who knows...
Well, that is it, here we are using gorgon to scare debuggers! That's all folks!
;)
I have decided to make an Aegis
' Go
bind because I am watching many applications related to information security
being written mainly on Go
, showing up during these years (2020). Who knows this bind can be useful for somebody
somewhere over a concurrent multiplatform goroutine rainbow... Go
is also my second best programming language so
I have done it for fun, too. It was a good excuse for using Cgo
.
Basically, you need to import aegis package from this repo:
import (
"github.com/rafael-santiago/aegis/gopkg"
)
After you will define in your go.mod
the following:
(...)
replace github.com/rafael-santiago/aegis/gopkg => github.com/rafael-santiago/aegis/gopkg/v2
(...)
You can also host it as a local package (no problem). Take a look how it can be done by taking a look at go.mod
from
Go
samples (gopkg/samples
).
This replace trick will allow you use Aegis
' future releases without needing be noisy into your own related code.
Renaming packages and all those MacGyver-like
incantations, ready to go...
The usage of Aegis
on Go
is almost the same of its usage in C
. Follow on reading if you are interested on it.
I am taking into consideration that you have already followed my notes about wait4debug
C sample. Doing it
on Go
is quite straightforward, too. It is only about testing the attachment state by calling aegis.HasDebugger()
oracle function, look:
//
// Copyright (c) 2020, Rafael Santiago
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.
//
package main
import (
"fmt"
"github.com/rafael-santiago/aegis/gopkg"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
go func() {
sigintWatchdog := make(chan os.Signal, 1)
signal.Notify(sigintWatchdog, os.Interrupt)
signal.Notify(sigintWatchdog, syscall.SIGINT|syscall.SIGTERM)
<-sigintWatchdog
fmt.Fprintf(os.Stdout, "\rinfo: ctrl + C received. Aborted.\n")
os.Exit(1)
}()
fmt.Fprintf(os.Stdout, "info: Waiting for debug attachment (pid=%d)...\n",
os.Getpid())
for !aegis.HasDebugger() {
time.Sleep(1 * time.Nanosecond)
}
fmt.Fprintf(os.Stdout, "\rinfo: Debug detected. Go home!\n")
}
The program will wait for a user's Ctrl + c
interruption or for a debugger attaching. It is always important to
sleep for some time interval, otherwise you will busy the main thread and cause starvation on other threads.
Well, if you have some artistic inclination and want to make a Medusa-like gopher
to put here I would be thankful haha!
Anyway, use Aegis Gorgon
in Go
is pretty straightforward, too. You just call aegis.SetGorgon()
. Take a look:
//
// Copyright (c) 2020, Rafael Santiago
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.
//
package main
import (
"fmt"
"github.com/rafael-santiago/aegis/gopkg"
"os"
"os/signal"
"syscall"
"time"
)
// INFO(Rafael): This function flags for aegis.SetGorgon() if it is time to stop watching and exit.
func shouldExit(exit interface{}) bool {
exitChan := exit.(chan bool)
var should bool = false
timeout := time.Tick(1 * time.Nanosecond)
select {
case should = <-(exitChan):
case <-timeout:
}
return should
}
func main() {
exit := make(chan bool, 1)
go func(exit chan<- bool) {
sigintWatchdog := make(chan os.Signal, 1)
signal.Notify(sigintWatchdog, os.Interrupt)
signal.Notify(sigintWatchdog, syscall.SIGINT|syscall.SIGTERM)
<-sigintWatchdog
exit <- true
fmt.Fprintf(os.Stdout,
"\rinfo: ctrl + c received from the user. Exiting...\n")
}(exit)
fmt.Fprintf(os.Stdout, "info: process started (pid=%d)...\n", os.Getpid())
// INFO(Rafael): Let's be less ambitious here. We will just pass our
// gracefully exit check function and its argument.
// OnDebugger and OnDebuggerArgs will be null, with this
// we will use the default Aegis' on debugger function
// that is only about a gross os.Exit(1) >:P
go aegis.SetGorgon(shouldExit, exit, nil, nil)
// INFO(Rafael): All your sensitive instructions not suitable for
// eavesdroppers would go here. On some requirements you will need
// to flush some buffers, files and stuff before exiting. For those
// cases you would pass your custom OnDebugger and OnDebuggerArgs
// to Aegis' Gorgon.
<-exit
}
The program will run until detecting a debugger be attached or being asked for gracefully exiting through a ctrl + C
.