22

Under Linux it's possible to trace exactly the kernel system calls with strace. ltrace can be used also to trace library calls. I wonder if it's possible to detect if my executable is running under strace or ltrace ?

Here's an example of the output of strace and ltrace for the diff executable.

strace

$ strace diff
execve("/usr/bin/diff", ["diff"], [/* 43 vars */]) = 0
brk(0)                                  = 0x110a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcbc13f6000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=122500, ...}) = 0
mmap(NULL, 122500, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fcbc13d8000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/librt.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340!\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=31784, ...}) = 0
mmap(NULL, 2129016, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fcbc0fce000
mprotect(0x7fcbc0fd5000, 2093056, PROT_NONE) = 0
mmap(0x7fcbc11d4000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7fcbc11d4000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1811160, ...}) = 0
mmap(NULL, 3925240, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fcbc0c0f000
mprotect(0x7fcbc0dc4000, 2093056, PROT_NONE) = 0
mmap(0x7fcbc0fc3000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b4000) = 0x7fcbc0fc3000
mmap(0x7fcbc0fc9000, 17656, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fcbc0fc9000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200l\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=135398, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcbc13d7000
mmap(NULL, 2212936, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fcbc09f2000
mprotect(0x7fcbc0a0a000, 2093056, PROT_NONE) = 0
mmap(0x7fcbc0c09000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7fcbc0c09000
mmap(0x7fcbc0c0b000, 13384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fcbc0c0b000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcbc09f1000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcbc09f0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcbc09ef000
arch_prctl(ARCH_SET_FS, 0x7fcbc09f0700) = 0
mprotect(0x7fcbc0fc3000, 16384, PROT_READ) = 0
mprotect(0x7fcbc0c09000, 4096, PROT_READ) = 0
mprotect(0x7fcbc11d4000, 4096, PROT_READ) = 0
mprotect(0x61b000, 4096, PROT_READ)     = 0
mprotect(0x7fcbc13f8000, 4096, PROT_READ) = 0
munmap(0x7fcbc13d8000, 122500)          = 0
set_tid_address(0x7fcbc09f09d0)         = 32425
set_robust_list(0x7fcbc09f09e0, 0x18)   = 0
futex(0x7fff27e5992c, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 1, NULL, 7fcbc09f0700) = -1 EAGAIN (Resource temporarily unavailable)
rt_sigaction(SIGRTMIN, {0x7fcbc09f8750, [], SA_RESTORER|SA_SIGINFO, 0x7fcbc0a01cb0}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {0x7fcbc09f87e0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7fcbc0a01cb0}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM_INFINITY}) = 0
brk(0)                                  = 0x110a000
brk(0x112b000)                          = 0x112b000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=7216624, ...}) = 0
mmap(NULL, 7216624, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fcbc030d000
close(3)                                = 0
sigaltstack({ss_sp=0x61c5e0, ss_flags=0, ss_size=8192}, NULL) = 0
open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcbc13f5000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2570
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0x7fcbc13f5000, 4096)            = 0
open("/usr/share/locale/en_US/LC_MESSAGES/diffutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/diffutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en_US/LC_MESSAGES/diffutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en/LC_MESSAGES/diffutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
rt_sigaction(SIGSEGV, {0x40b3c0, [], SA_RESTORER|SA_STACK|SA_NODEFER|SA_RESETHAND|SA_SIGINFO, 0x7fcbc0c454a0}, NULL, 8) = 0
write(2, "diff: ", 6diff: )                   = 6
write(2, "missing operand after `diff'", 28missing operand after `diff') = 28
write(2, "\n", 1
)                       = 1
write(2, "diff: ", 6diff: )                   = 6
write(2, "Try `diff --help' for more infor"..., 39Try `diff --help' for more information.) = 39
write(2, "\n", 1
)                       = 1
exit_group(2)                           = ?

ltrace

$ ltrace diff
__libc_start_main(0x402310, 1, 0x7fff876fcf28, 0x4151d0, 0x415260 <unfinished ...>
strrchr("diff", '/')                                             = NULL
setlocale(6, "")                                                 = "en_US.UTF-8"
bindtextdomain("diffutils", "/usr/share/locale")                 = "/usr/share/locale"
textdomain("diffutils")                                          = "diffutils"
sigaltstack(0x7fff876fccd0, 0, 1, 0x736c6974756666, 3)           = 0
dcgettext(0, 0x4183d7, 5, -1, 3)                                 = 0x4183d7
dcgettext(0, 0x4183e5, 5, 0, 1)                                  = 0x4183e5
sigemptyset(0x7fff876fccf8)                                      = 0
sigaction(11, 0x7fff876fccf0, NULL)                              = 0
re_set_syntax(264966, 0x7fff876fcb88, 0, -1, 0)                  = 0
malloc(16)                                                       = 0x016e1160
memset(0x016e1160, '\000', 16)                                   = 0x016e1160
getopt_long(1, 0x7fff876fcf28, "0123456789abBcC:dD:eEfF:hHiI:lL:"..., 0x00417360, NULL) = -1
malloc(1)                                                        = 0x016e1180
dcgettext(0, 0x415598, 5, 8, 3)                                  = 0x415598
error(0, 0, 0x415598, 0x7fff876fd469, 1diff: missing operand after `diff'
)                         = 0
dcgettext(0, 0x415878, 5, 0, 0x7fad1793c700)                     = 0x415878
error(2, 0, 0x415878, 0x7fff876fd469, 1diff: Try `diff --help' for more information.
 <unfinished ...>
+++ exited (status 2) +++

3 Answers 3

28

ptrace can be detected by the fact that a process can only call ptrace once. if ptrace() was already called by the strace executable or a debugger, we can detect it in runtime.

#include <stdio.h>
#include <sys/ptrace.h>

int main()
{
    if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) 
    {
        printf("don't trace me !!\n");
        return 1;
    }
    // normal execution
    return 0;
}

However, it's not hard to break this code. first, it's possible just to NOP the ptrace() call. Second, it's possible to replace ptrace() call with our own ptrace() call using LD_PRELOAD

4
  • 6
    Another way to work around with gdb is to step over the ptrace call (next) and to set the %eax to zero afterward (set $eax = 0).
    – perror
    Commented Apr 25, 2013 at 18:24
  • 2
    Another way is to jump over the code or set $pc to skip it.
    – 0xC0000022L
    Commented Apr 27, 2013 at 3:15
  • 1
    Of course, you could just check for LD_PRELOAD. Or make it setuid.
    – Antimony
    Commented Feb 17, 2014 at 18:34
  • This will cause the process to be stopped if it invokes execve(2).
    – dlitz
    Commented Oct 7, 2018 at 22:12
13

Apart from the ptrace trick, you can check /proc/PID/cmdline, raise a SIGTRAP, use getppid, ...

You may want to check pangu (disclamer: I'm the author).

Pangu a a little toolset to mess around with debugging-related tools from the GNU project, and especially on GNU/Linux x86.

2
  • Did you manage to compile the asm for Android ndk? I'm getting bad instructions error
    – xDragonZ
    Commented Jan 23, 2016 at 23:40
  • 1
    Compile it for x86 and not arm
    – jvoisin
    Commented Jan 26, 2016 at 17:41
5

It is possible the ways the other commenters suggested. However, all of them are interceptable, if you read a file, it can be intercepted and your program will be given different result, if you check if LD_PRELOAD is set, it can be unset before you can reach out to it, etc. Suid is another story, though, but there may be different ways of exploiting it too.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.