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.
Contents
How can I build it?
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.
Aegis
?
How should I easily clone The easiest way is:
black-beard@QueensAnneRevenge:~/src# git clone https://github.com/rafael-santiago/aegis --recursive
black-beard@QueensAnneRevenge:~/src/aegis/src# _
Hefesto
Building it by using 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.
make
Poor man's build by using 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
.
Making a distribution package
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-
.
Aegis
Go
stuff?
How should I build 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
Using 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!
Debugging detection
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# _
wait4debug
Testing 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# _
Debugging mitigation
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.
setgorgon
Testing 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!
;)
Aegis
from Go
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/v1
(...)
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.
wait4debug
on Go
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.
Gopher Gorgon
?
What about a 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
.
Contributors
The following table lists all project's contributors until now.
GitHub profile | Who | Contact | Contributions |
---|---|---|---|
![]() |
Rafael Santiago | /dev/null |
Initial idea, C library , initial cgo-bind , current maintainer. |