Strace

Nasıl Çalışır?

Unix tabanlı sistemlerde strace gibi bir uygulamanın varolabilmesi için gereken ptrace sistem çağrısı uzun yıllardır (SVr4 ve 4.3BSD) bulunmaktadır.

Sistem çağrısı ismini process trace kavramından alır. ptrace sistem çağrısı üzerinden bir uygulama başka bir uygulamanın durumunu takip edebildiği gibi değişiklikler yapma imkanına da sahip olmaktadır.

ptrace sistem çağrısı temel olarak gdb gibi debug uygulamalarında, strace, ltrace gibi sistem veya kütüphane çağrılarını takip uygulamalarında, code coverage araçlarında, çalışan yazılım koduna dokunmadan bazı hataların giderilmesinde veya güvenlik kontrollerinden geçirilmesinde kullanılır.

ptrace çağrısıyla bir uygulamanın kontrolü tamamen başka bir uygulama verilmektedir. Buradaki kontrolden kastımız, uygulamanın kullandığı tüm bellek alanına erişim, sinyallerin alınması, değiştirilmesi, file descriptor'ların yönetimi hatta uygulamanın kod segmentinin değiştirilerek yamalar yapılması dahil aklımıza gelebilecek hemen her türden tehlikeli değişikliklere izin veriliyor olmasıdır.

Bahsettiğimiz bu özelliklerinden ötürü, bir uygulamanın başka bir uygulamayı ptrace ile kontrol edebilmesi için, ilgili uygulamaya sinyal gönderme yetkisinin bulunması gerekir. Dolayısıyla özel durumlar haricinde her kullanıcının kendi sahibi olduğu diğer uygulamaları ptrace ile kontrol edebileceğini, root kullanıcısının da sistemdeki tüm uygulamaları kontrol edebileceğini söyleyebiliriz.

Linux Capabilities API sisteminin geliştirilmesinden sonra yukarıda koşullardan bağımsız olarak, CAP_SYS_PTRACE özelliği sayesinde de ptrace izni verilebilmektedir.

Tipik Kullanım

Uygulamanızı strace ile aşağıdaki biçimde başlatmanız yeterlidir:

$ strace ls /tmp

Ancak çoğu zaman çok daha önceden başlatılmış ve çalışmaya devam eden, bununla birlikte herhangi bir sorun nedeniyle ek bilgi toplamak istediğiniz durumlar oluşur. strace ile herhangi bir çalışan uygulamaya, -p parametresine <PID> değerini vermek suretiyle attach olabiliriz:

$ strace -p $(pidof mysqld)
Process 26829 attached - interrupt to quit
select(13, [10 12], NULL, NULL, NULL)   = 1 (in [10])
fcntl64(10, F_SETFL, O_RDWR|O_NONBLOCK) = 0
accept(10, {sa_family=AF_INET, sin_port=htons(33033), sin_addr=inet_addr("192.168.0.15")}, [16]) = 34
fcntl64(10, F_SETFL, O_RDWR)            = 0
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART}, {SIG_DFL, [CHLD], SA_RESTART}, 8) = 0
getpeername(34, {sa_family=AF_INET, sin_port=htons(33033), sin_addr=inet_addr("192.168.0.15")}, [16]) = 0
...

-f Parametresi

Strace'in uygulamanın tüm thread'lerini ve uygulamadan fork() edilen diğer çocuk süreçlerini takip edebilmesi için -f parametresi verilmelidir.

$ strace -f ./example

$ strace -f -p $(pidof mysqld)

-e Parametresiyle Filtreleme

Strace çıktısı zaman zaman takip için oldukça kalabalık olabilir.

Sadece belirli sistem çağrılarını takip etmek istiyorsanız e parametresi ile bunu yapabilirsiniz:

$ strace -f -e trace=open,write,close,connect,select -p 3245

Sadece dosya işlemleriyle ilgili sistem çağrılarını takip etmek için -e trace=file kullanılabilir:

$ strace -e trace=file 4535

Sadece network ile ilgili sistem çağrılarını filtrelemek için -e trace=network kullanılabilir:

$ strace -e trace=network 23232

Zaman Bilgisi Alma: -tt

Sistem çağrılarının çıktısı alınırken saniye hassasiyetinde zaman bilgisi de almak istiyorsak -t parametresini kullanabiliriz.

Çoğu zaman saniye hassasiyeti yeterli olmayacaktır. Mikrosaniye hassasiyetinde zaman bilgisi almak için -tt parametresini kullanabiliriz:

$ strace -tt ls /tmp
...
00:07:10.595807 openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
00:07:10.595885 getdents(3, /* 6 entries */, 32768) = 176
00:07:10.595977 getdents(3, /* 0 entries */, 32768) = 0
00:07:10.596041 close(3)

İstatistiki Bilgi Alma: -c

-c parametresi ile istediğimiz süre boyunca sistem çağrılarıyla ilgili istatistik toplayabiliriz:

$ strace -f -c -p $(pidof mysqld)
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
42.89    0.592035         275      2149       151 read
28.11    0.388024        2587       150        18 futex
27.82    0.384023        1352       284           select
 1.16    0.016001        3200         5           rt_sigtimedwait
 0.01    0.000154           0      1457           write
 0.01    0.000152          22         7           accept
 0.00    0.000007           0      9478           time
 0.00    0.000000           0       108           open
 0.00    0.000000           0       136           close
 0.00    0.000000           0        40           unlink
 0.00    0.000000           0         5           alarm
 0.00    0.000000           0         9           access
 0.00    0.000000           0        21           ioctl
 0.00    0.000000           0      2409           gettimeofday
 ................

-o İle Çıktıların Kayıt Edilmesi

strace uzun süreli çalıştırılıp oluşan loglar daha detaylı olarak geniş zaman aralığında incelenecekse, logların kayıt edilmesi gerekecektir.

-o parametresi ile logların kayıt edileceği dosyayı belirtebilirsiniz:

$ strace -f -o /tmp/strace.log -e trace=file ls /tmp

Ptrace Engelleme

Linux altında bir uygulamanın, kendisinin root harici kullanıcılar tarafından ptrace sistem çağrısı ile kontrol edilmesini engelleyebilmesine imkan verilmiştir.

Bu işlem için prctl özel sistem çağrısı kullanılır. Uygulama prctl aracılığıyla kendisi için PR_SET_DUMPABLE bayrağını temizleyecek olursa root haricindeki kullanıcıların uygulamaya sinyal gönderme hakkı olsa dahi bu uygulamayı ptrace ile kontrol etme şansları olmaz.

Bu özelliğin en tipik kullanımlarından biri, OpenSSH authentication agent yazılımında görülür. Böylelikle kullanıcıların parola girme aşamasında uygulamanın ptrace ile başka bir uygulama tarafından kontrolü engellenmiş olur.

Güvenlik

Linux kullanımının yaygınlaşmasıyla birlikte zararlı yazılımlara rastlanma sıklığı da artmaktadır. Geleneksel Linux process modelindeki ptrace imkan seti sebebiyle, sisteminizde kendi kullanıcınızla çalıştırdığınız herhangi bir yazılım içerisine zararlı bir kod enjekte edilmiş ise (en basit xterm aracından gelişmiş web tarayıcı uygulamalarına kadar), ptrace sistem çağrısı sayesinde çalışan diğer tüm uygulamalarınızın kontrolünün bu zararlı yazılım tarafından devralınması ve siz hiç bir şey farketmeden önemli bilgilerin kopyalanması mümkündür.

Pek çok kullanıcının farkında olmadığı bu duruma karşılık, Linux çekirdeği içerisindeki Yama kod adlı güvenlik modülüyle bir koruma mekanizması geliştirilmiştir. (Ayrıntılı bilgiler için: https://www.kernel.org/doc/Documentation/security/Yama.txt)

Yama modülü olan Linux çekirdeğinde, /proc/sys/kernel/yama/ptrace_scope dosyası üzerinden ptrace sistem çağrısına verilecek tepki kontrol altına alınabilmektedir. Öntanımlı olarak bu dosyada 0 değeri yazmaktadır. Dosyada yazan değer aşağıdaki tablo doğrultusunda yorumlanır:

Değer Anlam
0 Geleneksel davranış: önceki bölümde anlatılanlar doğrultusunda ptrace yapabilme hakkının bulunduğu tüm uygulamalar kontrol edilebilir
1 Kısıtlandırılmış ptrace: sadece uygulamanın doğrudan parent process'lerine veya PR_SET_PTRACER opsiyonuyla uygulama tarafından izin verilen debug uygulamalarına ait PID değerlerinin eşleştiği uygulamalara kontrol izni verilir. Böylece gdb program_adi ve strace program_adi şeklindeki kullanımlar çalışmaya devam eder ancak çalışan bir uygulamaya sonradan attach olmaya izin verilmeyecektir (dolayısıyla strace -p PID yöntemi de çalışmayacaktır). Diğer opsiyon da özellikle KDE, Chromium, Wine gibi uygulamaların kullandığı, debug/crash handler'a ait PID değerinin PR_SET_PTRACER ile uygulama içerisinden belirtilmesi ve bu sayede spesifik bir uygulamanın ptrace yapabilmesi şeklindedir
2 Sistem yöneticisine ptrace: sadece CAP_SYS_PTRACE özelliği tanımlanmış uygulamalar veya prctl ile PTRACE_TRACEME opsiyonunu tanımlayan çocuk process'ler kontrol edilebilir
3 Tamamen devre dışı: hiç bir şart altında ptrace yapılmasına imkan tanınmaz. Bu özellik bir defa tanımlandığı takdirde çalışma anında tekrar değişiklik yapılamaz

Her ne kadar uygulamalar prctl üzerinden kendilerinin ptrace yapılabilmesini root kullanıcısı dışında devre dışı bırakabiliyor olsalar da, pek çok yazılımcı bu detayların farkında değildir. OpenSSH agent'ı gibi doğrudan güvenlikle ilgili yazılımlar bu işlemleri yapıyor olmasına karşın, sistemde çalışan tüm yazılımlardan aynı davranışı beklemek doğru olmaz. Bu nedenle sistem genelinde yazılımdan bağımsız çözümlerin üretilmesi önem taşır. Son zamanlarda bazı Linux dağıtımları (Ubuntu vb.) yukarıda tariflediğimiz ptrace_scope dosyasının öntanımlı değerini 1 yapmaya başlamışlardır. Böylelikle ptrace işlemleri kısıtlandığından sistem genelinde daha güvenli bir çalışma ortamı sağlanmaktadır.

Android kullanan sistemler de düşünüldüğünde bu gibi güvenlik konularında daha hassas olunması gerektiği açıktır.

Örnek strace gerçekleştirimi

Aşağıdaki örnek uygulamayı ministrace.c adıyla kaydedip

$ gcc -m32 -o ministrace ministrace.c

ile derleyebilirsiniz.

Not: Örnek uygulama 32 bitlik sistemler için yazılmış olup mimari farklılıkları gözardı edilmiştir. Bu sebeple 32 bitlik bir sistemde veya gcc-multilib kurulu ise -m32 parametresi ile derlemelisiniz.

#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

int wait_for_syscall (pid_t child)
{
    int status;
    while (1) {
        ptrace(PTRACE_SYSCALL, child, 0, 0);
        waitpid(child, &status, 0);
        if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80)
            return 0;
        if (WIFEXITED(status))
            return 1;
    }
}

int do_child (int argc, char **argv)
{
    char *args [argc+1];
    memcpy(args, argv, argc * sizeof(char*));
    args[argc] = NULL;

    ptrace(PTRACE_TRACEME);
    kill(getpid(), SIGSTOP);
    return execvp(args[0], args);
}

int do_trace (pid_t child)
{
    int status, syscall, retval;
    waitpid(child, &status, 0);
    ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
    while(1) {
        if (wait_for_syscall(child) != 0) break;

        syscall = ptrace(PTRACE_PEEKUSER, child, sizeof(long)*ORIG_EAX);
        fprintf(stderr, "syscall(%d) = ", syscall);

        if (wait_for_syscall(child) != 0) break;

        retval = ptrace(PTRACE_PEEKUSER, child, sizeof(long)*EAX);
        fprintf(stderr, "%d\n", retval);
    }
    return 0;
}

int main (int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "Usage: %s prog args\n", argv[0]);
        exit(1);
    }

    pid_t child = fork();
    if (child == 0) {
        return do_child(argc-1, argv+1);
    } else {
        return do_trace(child);
    }
}

Uygulama derlendikten sonra herhangi bir komutu ministrace ile çalıştırıp çıktıyı inceleyebiliriz:

$ ./ministrace date
syscall(11) = 0
syscall(12) = 21843968
syscall(21) = -2
syscall(9) = 164245504
...
syscall(9) = 164241408
syscall(1) = Tue Mar  3 13:44:52 EET 2015
29
syscall(3) = 0
...

Örnek uygulamamızda 65 satırlık bir kod ile strace uygulamasının temel çalışma prensibi gösterilmeye çalışılmıştır. Daha gelişmiş bir örnekte sistem çağrılarının numaralarından isimlerine ulaşmak, çağrıda kullanılan parametreleri ve geri dönüş kodlarının ilgili sistem çağrısı özelinde anlamlarını göstermek mümkün olabilir.

results matching ""

    No results matching ""