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.