asstrace
stands for a stateful strace-like - Linux syscall tampering-first strace
-like tool.
As opposed to strace
, asstrace
is more of a framework tool than a debugging tool. If your goal is to see why some black-box binary is not working, then strace
with all it's advanced features is the way to go.
asstrace
is designed to provide a convenient way of altering binary behavior and sharing it to other people.
It doesn't change the binary itself, but allows for manipulating behavior of system calls that the binary executes.
asstrace
is designed to work with Linux
. Adding support for a new architecture is as simple as adding arch/<arch name>.h
file. Currently x86
and RISC-V
are supported.
-
legacy executable which source code is not available no longer works on modern workstations, as it assumes presence of some special files (sockets, device character special etc.). We can intercept all system calls touching that particular device and provide our own implementation that emulate the device (all the emulation is in user mode).
-
legacy black-box executable does not work because inside a binary there are IP address and port hardcoded, that are no longer accessible as the service moved to a different server. We can intercept network system calls that try to access non-existing address, and change it so that the new address is used.[sample run] [source code]
-
black-box executable does some computation, and as a result it creates a single output file. During computation it creates lots of meaningful temporary files, but unfortunately it deletes them all before output is produced. Using
asstrace
we can intercept allunlink
system calls and cause them to do nothing. This way no temporary files get removed! [sample run] [source code]
In this example we run g++ ./asstrace.cc
, but prevent it from deleting temporary files.
myuser@myhost:~/asstrace$ make example_unlink
g++ -rdynamic -fpermissive asstrace.cc -o asstrace
make -C examples unlink
g++ -I.. -shared -fPIC unlink.cc -o libunlink.so
../asstrace ./libunlink.so g++ ../asstrace.cc 2>/dev/null | grep prevented
>> prevented /tmp/ccpiWX9G.res from removing!
>> prevented /tmp/cckCJl7b.o from removing!
>> prevented /tmp/ccVwSoa6.s from removing!
myuser@myhost:~/github/asstrace$
myuser@myhost:~/github/asstrace$ file /tmp/cckCJl7b.o
/tmp/cckCJl7b.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
We managed to prevent GCC from removing artifacts from /tmp/
directory. See source code
In this example we run two processes: background nc -l -p 8000
(server listening on 127.0.0.1:8000
), and a echo payload | nc -N 1.1.1.1 80
that tries to connect to 1.1.1.1:80
and send it some payload.
Of course there is an address mismatch, so server won't receive any data, until we tamper client execution with asstrace
.
myuser@myhost:~$ make example_net
g++ -rdynamic -fpermissive asstrace.cc -o asstrace
make -C examples network_forwarding
bash -c "nc -l -p 8000 ; echo NETCAT SERVER RECEIVED DATA!" &
g++ -I.. -shared -fPIC network_forwarding.cc -o libnet.so
echo "<I am the payload>" | ../asstrace ./libnet.so nc -N 1.1.1.1 80 2>/dev/null
>> network forwarding: 1.1.1.1:80 -> 127.0.0.1:8000
<I am the payload>
NETCAT SERVER RECEIVED DATA!
myuser@myhost:~$
myuser@myhost:~$
With asstrace
in the loop server successfully read the data. See source code.
myuser@myhost:~/asstrace$ make
g++ -shared -fPIC filter.cc -o libfilter.so # compile application-specific user library
g++ -rdynamic -fpermissive asstrace.cc -o asstrace # compile generic 'asstrace' engine
make run
below will run ./asstrace ./libfilter.so cat asstrace.cc
twice, showing either stdout
only or stderr
only.
user@host:~/asstrace$ make run
./asstrace ./libfilter.so cat asstrace.cc 2>/dev/null | head
#INCLUDE <STDIO.H>
#INCLUDE <STDLIB.H>
#INCLUDE <STRING.H>
#INCLUDE <UNISTD.H>
#INCLUDE <SYS/TYPES.H>
#INCLUDE <SYS/WAIT.H>
#INCLUDE <SYS/USER.H>
#INCLUDE <SYS/SYSCALL.H>
#INCLUDE <ASSERT.H>
#INCLUDE <DLFCN.H>
-------------------------------------
./asstrace ./libfilter.so cat asstrace.cc 2>&1 >/dev/null | head
execve(0x7ffc0bc327e0, 0x7ffc0bc32dc8, 0x7ffc0bc32de0, ) = 0xfffffffffffffffe
execve(0x7ffc0bc327e0, 0x7ffc0bc32dc8, 0x7ffc0bc32de0, ) = 0xfffffffffffffffe
execve(0x7ffc0bc327e0, 0x7ffc0bc32dc8, 0x7ffc0bc32de0, ) = 0xfffffffffffffffe
execve(0x7ffc0bc327e0, 0x7ffc0bc32dc8, 0x7ffc0bc32de0, ) = 0xfffffffffffffffe
execve(0x7ffc0bc327e0, 0x7ffc0bc32dc8, 0x7ffc0bc32de0, ) = 0xfffffffffffffffe
execve(0x7ffc0bc327e0, 0x7ffc0bc32dc8, 0x7ffc0bc32de0, ) = 0xfffffffffffffffe
execve(0x7ffc0bc327e0, 0x7ffc0bc32dc8, 0x7ffc0bc32de0, ) = 0x0
brk(0x0, ) = 0x5600c1c0c000
arch_prctl(0x3001, 0x7ffc628086a0, ) = 0xffffffffffffffea
mmap(0x0, 0x2000, 0x3, 0x7fe9e6effcd7, 0xffffffff, 0x0, ) = 0x7fe9e6ed7000
We provide an example user library (libfilter.so
) that is designed to cause cat <filename>
program to show the contents of <filename>
, but make it uppercase. It does the following steps:
- It intercepts only
read
syscalls. - On each
read(fd, ...)
, it checks to which file/proc/pid/fd
points to. - If
fd
points to<filename>
it does special action, otherwise it allowscat
program to executeread
as usual. - Special action goes as follows: If this is a first
read(fd, buf, count)
to that file then open the<filename>
itself (otherwise is is already opened), and read up tocount
bytes from it to some temporary buffer. Then transform all the ASCII characters from that buffer to uppercase, then copy it back to tracee's (cat
) address space (using helperapi_memcpy_to_tracee
). Thecat
program returns fromread
syscall with buffer filled with uppercase data from<filename>
.
See user guide for more details.