Giriş
~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~
Bu yazıda ELF dosyalarının nasıl encryption yapılacağı
üzerine konuşacağız. Yazmış olduğum bir encryption programını sizlere adım adım
anlatacağım. Öncelikle ELF hakkında temel
bilgiler sunulacak ardından uygulamanın anlatımına geçilecektir. Burada
XOR yöntemi kullanılarak encryption gerçekleştirilmiş olsa da mühim olan bu yöntemin
sağlıklı bir şekilde ELF dosyasına uygulanabilmesidir. Bu yüzden olayın
mantığını anladıktan sonra encryption metodolojisini değiştirmek sizin keyfinize kalmıştır.
Elf dosyasının ilk 52 baytlık alanı ELF başlığından oluşmaktadır. Elf çalıştırılabilir olan dosyalar için Elf başlığı olması gereken zorunlu bir başlıktır.
Elf başlığının kendisine ilişkin veri yapısı elf.h başlığı içerisinde tanımlanmıştır. Elf başlığına ilişkin veri yapısı şu şekildedir.
e_entry, Elf dosyasının entypoint/başlangıc adresini belirtir. İşletim sistemi dosyayı belleğe yükledikten sonra e_entry içerisinde tanımlanmış sanal adresden dosyayı çalıştırmaya başlatır.
e_phoff, Program başlık
tablosunun dosya içerisindeki offset değerini belirtir.
e_shoff, Section başlık
tablosunun dosya içerisindeki offset değerini belirtir.
e_ehsize, Elf başlık
dosyasının boyutunu bayt cinsinden belirtir.
e_phentsize, program
başlık tablosu segment kayıtlarından oluşmaktadır. Elf structının bu üyesi program başlık
tablosunun bir kayıtının bayt cinsinden boyutunu belirtir. Her kayıt aynı
boyuttadır.
e_phnum, program başlık tablosunun kaç adet kayıta
sahip olduğunu belirtir.
e_shentsize, section
başlık tablosu section kayıtlarından oluşmaktadır. Elf başlığının bu üyesi section başlık
tablosunun bir kayıtının bayt cinsinden boyutunu belirtir. Her kayıt aynı
boyuttadır.
e_shnum, section başlık
tablosunun kaç adet kayıta sahip olduğunu belirtir.
Section Header
~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~
ELF dosyalarının tamamı
direk olarak çalıştırılabilir dosyalar değildir. Örneğin, Elf dosyaların
bazıları relocatable türünde olabilir ve
birden fazla relocatable dosya birleşerek yeni bir executable dosya
oluşturabilir. Linker birden fazla relocatable dosyasını sağlıklı bir
şekilde birleştirmek için section başlık
tablosuna göz atar ve istenilen elf türündeki dosyayı oluşturur.
Section başlık tablosu, ELF dosyasındaki sectionlar için tutulan bir tablodur. Bu tablonun dosya üzerinde nerede olduğunu bulabilmek için ELF headerındaki e_shoff değerine bakılır.
Section başlık
tablosundaki bazı önemli sectionlar
şunlardır.
Text section, Elf
dosyasının kodlarının tutulduğu
kısımdır.
Data section,
ilklendirilmiş global değişkenlerin tutulduğu kısımdır.
Bss section, ilk değer
ataması gerçekleştirilmemiş değişkenlerin tutulduğu kısımdır.
Symtab
section, fonksiyon ve değişken isimleri gibi bilgiler birer semboldür ve bu
sembolere ilişkin bilgi bu bölümde tutulur.
Elf.h başlığı içerisinde bulunan section tablosundaki kayıtları ilişkin structın yapısı şu şekildedir.
Elf.h başlığı içerisinde bulunan section tablosundaki kayıtları ilişkin structın yapısı şu şekildedir.
sh_name,
section isminin string tablosundaki index değeridir.
sh_type,
section türünü belirtir. Progbits, symtab, dynsym, dynamic section türlerinden
bazılarıdır.
Section
türü progbits olan bir section içerisinde kod,data ve debugger bilgileri
içerir.
Section
türü symtab ve dynsym olan bir section sembol tablosunu içinde barındırır.
Section türü dynamic
olan bir section dinamik linker için bir takım bilgiler içerir.
sh_addr,
elf dosyası belleğe yüklenirken bu
sectionın yükleneceği başlangıc sanal adresini belirtir.
sh_offset,
sectionın disk üzerindeki offset bilgisini belirtir.
sh_size,
sectionın boyutunu bayt cinsinden ifade eder.
sh_addralign,
sectionın belleğe haritalanırken hizalanma şeklini belirtir. bu section sh_addr
sanal adresinden itibaren sh_addraling katı şeklinde belleğe hizalanır.
sh_entsize,
sectionın kayıt uzunluğunu belirtir. Tüm sectionlar section başlık tablosu
içerisinde aynı kayıt uzunluğundadırlar.
ELF
Program Header
~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~
Program başlık tablosu
çalışma zamanında kesinlikle bulunması gereken tablodur. Section Tablosu,
linker tarafından birden fazla elf dosyasını birbirine bağlarken göz atılır. Fakat linux işletim sistemi bir
dosyayı çalıştırmak için program başlık tablosuna ihtiyaç duyar ve bu tablonun
olmaması durumunda linux işletim sistemi elf dosyasını çalıştıramaz.
Segmentler
bir veya birden fazla sectionın birleşmesinden oluşur.
p_type,
segmentin türünü belirtir. linux işletim sistemi dosyayı belleğe yüklerken
segment türlerine bakarak bu işlemi yapar.
PT_LOAD, bu segmentin
yüklenebilir bir segment olduğunu belirtir. Bu segment için tanımlı taban adres
ve segmentin boyutu hesaplanarak bu segment belleğe yüklenir.
PT_DYNAMIC, bu segment
dinamik linkleme aşamasında ihtiyaç duyulan bilgileri içerir.
PT_INTERP, bu segment
dinamik linkerın tam yolunu string olarak belirtir.
PT_PHDR, bu segment
program header tablosunun kendisini içerir.
p_offset, bu segmentin disk üzerindeki
dosyanın neresinde bulunduğu belirten offset değerini içerir.
p_vaddr, bu segmentin virtual memoryde nereden itibaren yükleneceğini belirten sanal adresdir.
p_filesz, bu segmentin
bayt cinsinden boyutunu ifade eder. Bu değer disk üzerindeki segmentin boyutunu
ifade eder.
p_memsz, bu segmentin
bellek boyutunu ifade eder. Eğer bu değer p_filesz değerinden büyükse bu
durumda işletim sistemi bu segment için gerekli alanları 0 değerine setler.
p_flags, segmentin
izinleri bu değer ile belirtirlir.
P_flags ile bir
segmentin çalıştırılabilir, okunabilir , yazılabilir izinleri belirtilir.
p_align , segment
belleğe yüklenirken bu değere göre
hizalanır. Eğer p_align 4 değerini içeriyorsa bu durumda bu segment taban
adresinden başlanarak 4 bayt şeklinde taban adresinden itibaren belleğe
yüklenmeye başlanılacaktır.
Statik Linklenmiş ELF Dosyalarının Belleğe Yüklenmesi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Statik
olarak linklenmiş elf executable dosyalarının ihtiyaç duyduğu tüm
kütüphaneler statik linker tarafından kendisine linkleme
aşamasında bağlanmıştır. Bu yüzden statik olarak linklenmiş dosyalar çalışma
esnasında çağrılacak fonksiyonların herhangi biri için adres çözümlemelerine
ihtiyaç duymadığından INTERP ve DYNAMIC segmentlerine ihtiyaç duymazlar.
işletim
sistemi, statik olarak linklenmiş elf dosyasını belleğe yüklerken program
başlık tablosundaki kayıtlardan p_type türü
PT_LOAD olan segmentleri belleğe yükler. PT_LOAD türü dışındaki diğer
segmentlerin belleğe yüklenmez.
Segmentlerin
belleğe yüklenmesi gerçekleştirildikten sonra ELF başlığında belirtilen giriş
adresinden program çalıştırılmaya başlanır.
Dinamik Linklenmiş ELF Dosyalarının Belleğe Yüklenmesi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dinamik
olarak linklenmiş elf executable dosyalarında
ihtiyaç duyulan kütüphanelerin tamamı statik linklenmiş dosyalardaki
gibi linkleme aşamasında bağlanmaz.
Fakat bunların kullanıldığı ve ihtiyaç duyulduğu bilgileri statik linker tarafından dinamik linklenmiş
elf executable dosyasının içerisinde
belirtilmiştir.
Dinamik
olarak linklenmiş elf executable dosyalar için program başlık tablosunda INTERP
ve DYNAMIC adında iki segment bulunmaktadır.
işletim
sistemi, program başlık tablosunu dolaşırken öncelikle PT_INTERP türündeki
INTERP segmentini aramaktadır. Elf executable dosyasının dinamik olarak
linklenmiş olması durumunda PT_INTERP türündeki INTERP segmenti bulunur.
Bu
segment çalışma esnasında ihtiyaç
duyulan dinamik linkerın tam yolunu belirtmektedir ve dinamik linker işletim
sistemi tarafından prosesin adres alanına yerleştirilir.
PT_LOAD
türündeki LOAD segmentlerin ve dinamik linkerın prosesin adres alanına yerleştirilmesinden sonra işletim sistemi
kontrolü statik linklenmiş
programlardaki gibi direk programın kendisine bırakmak yerine dinamik
linkera bırakır.
İşletim
sistemi dinamik linkera kontrolü bırakmadan önce bir takım yardımcı bilgileri
prosesin stack alanına iter. Bu yardımcı bilgiye auxiliary vektörü
denilmektedir.
Dinamik linker program hakkında gerekli
bilgileri kernel tarafından stack
üzerine itilmiş auxiliary vektöründen elde eder.
Auxiliary Vektörüne ilişkin bilgiler ;
AT_EXECFD,
programın file descriptor numarasını belirtir.
AT_PHDR,
belleğe yüklenen program için program başlık tablosunun adresini belirtir.
AT_PHTENT,
program başlık tablosunun kayıtlarının uzunluğunu belirtir.
AT_PHNUM,
program başlık tablosundaki kayıtların adetini belirtir.
AT_PAGESZ,
işletim sisteminin kullandığı sayfa boyutunu belirtir.
AT_BASE,
dinamik linkerın kendisinin yüklendiği taban adresi belirtir.
AT_ENTRY,
programın giriş adresini belirtir.
AT_UID,
programın gerçek user id numarasını belirtir.
AT_EUID,
programın geçici user id numarasını belirtir.
AT_GID,
programın gerçek group id numarasını belirtir.
AT_EGID, programın
geçici group id numarasını belirtir.
Dinamik
Linker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dinamik linker, auxiliary vektörü
aracılığı ile çalıştırılacak dinamik elf executable dosya hakkında bilgi sahibi
olur. Statik linker dinamik linklenmiş elf executable için DYNAMIC segmenti
içerisinde dosyanın ihtiyaç duyduğu kütüphaneleri belirtir.
DYNAMIC segmenti içerisindeki
kayıtlardan NEEDED türüne ait olanlar kütüphaneleri belirtmektedir ve dinamik
linker bu kütüphaneleri LD_LIBRARY_PATH çevre değişkenin belirttiği path
listesi içerisinde arar . Bu path
listesi içerisinde olmaması durumunda
sırasıyla /lib, /usr/lib klasörleri içerisinde aramaktadır.
Dinamik linker, kütüphaneleri
aradığı yerde bulması durumunda
programın adres alanına yükler.
Position
Independent Code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Position independent code yöntemi ile kodlanmış bir
uygulama belleğin istenilen adres
alanına yüklenir.
Program
ön tanımlı bir adres baz alınarak
yazılmaz. Emirlerin kendi aralarındaki uzaklığının, kısacası göreceli uzaklık durumu göz
önünde bulundurularak yazılır.
Bu
kodlama biçiminde göreceli call emri gibi atlama emirleri kullanılarak
programın yüklendiği adres elde edilir.
iki
kodlama biçiminden non-position indepedent code olan, eğer işletim sistemi
tarafından 0x0 adresinden itibaren yüklenmezse
bu durumda non-position independent code ile yazılmış program jmp emrini
doğru bir şekilde gerçekleştiremeyecektir.
Bu
durumda kodun akışı bozulacaktır veya atlanılan adres geçerli makine kodu
içermediğinden illegal emir hatası alınacaktır.
Position
independent code ile yazılmış program ise belleğin hangi alanına yüklenirse
yüklensin düzgün bir şekilde çalışacaktır.
çünkü
buradaki kodlamada göreceli call emirinin çalışma biçiminden
yararlanılmıştır. Göreceli call emirleri kendinden bir sonraki adrese göre
göreceli atlama gerçekleştirir.
göreceli
call emrinin çalışma biçimi şu şekildedir :
- bir
sonraki adresi stack alanına it.
- bir
sonraki adres ile operand değerini topla.
- toplama
işlemi sonucu elde edilen adresi instruction pointerın içerisine yerleştir.
Self-Modifying Code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Self Modifying code,
programın kendi kendisini runtime/çalışma esnasında değiştirebilen kodlama biçimidir. Bu kodlama
biçimiyle uygulama belleğe yüklendikten
sonra dinamik olarak kodlama
yapılabilir. Bu kodlama biçimi performans açısından kötü
sonuçlar doğuracağından kullanılmaz.
Bu kodlama biçiminde
üzerinde değişiklik yapılacak segmentlerin Read,Write,Execute haklarına sahip
olmalıdır.
Self-Modifying Code
için örnek kodlama aşağıdaki gibidir. Aşağıdaki kod parçasının belleğin 0.adresinden itibaren yüklendiği
varsayılmıştır.
MOV [0xB],EDX emiri ile
kendisinden sonraki 4 baytlık alan NOP emri ile değiştirilmektedir. Bu durumda
pop ebp, pop ecx, pop edx, ret emirleri MOV [0xB],EDX emrinin çalışmasından
sonra NOP (0x90) emirlerine
dönüşecektir.
Uygulama
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dosya üzerinde işlem
yapmaya başlanılmadan önce bir takım bilgilerin saklanması ve bu bilgiler
üzerinden hesaplama yapılması gerekmektedir.
Bu bilgilerin bazıları
dosya üzerinde işlem yapılırken ihtiyaç duyulmaktadır ve bazı bilgilere ise dosya belleğe
yüklendikten sonra ihtiyaç duyulmaktadır.
Bu bilgileri tutmak
için Info adında bir veri yapısı tanımlanmıştır.
struct info{
Elf32_Addr old_entry;
Elf32_Off offset;
Elf32_Word endofsegment;
Elf32_Addr rva;
Elf32_Addr text_rva;
int text_distance;
int ep_distance;
int size;
char xor_key;
};
old_entry, dosyanın
orjinal giriş adresi burada tutulur.
offset, son LOAD segmentin başlangıç offset bilgisi burada tutulur.
endofsegment, son LOAD segmentin son offset bilgisi burada
tutulur.
rva, son LOAD segmentin
taban virtual adresi burada tutulur.
text_rva , text
sectionın taban virtual adresi burada tutulur.
text_distance, inject kodunun ilk baytının adresi ile text
sectionın taban virtual adresi arasındaki fark burada tutulur.
ep_distance, inject
kodunun(ileride açıklanmıştır) ilk baytının adresi ile dosyanın orjinal giriş
adresinin arasındaki fark burada tutulur.
size, text sectionın
uzunluk bilgisi burada tutulur.
xor_key,şifrelemede
kullanılan anahtar burada tutulur.
Uygulamada Öncelikle Elf dosyasının header bilgisi okunur.
void read_elf(Elf32_Ehdr *ELF,int fd){
read(fd,ELF,sizeof(Elf32_Ehdr));
}
EHT=(Elf32_Ehdr *)malloc(52);
read_elf(EHT,fd);
Kodun tamamına ulaşmak için şuraya tıklayabilirsiniz.
read(fd,ELF,sizeof(Elf32_Ehdr));
}
EHT=(Elf32_Ehdr *)malloc(52);
read_elf(EHT,fd);
Daha sonra Program Header ve Section Header tabloları doldurulur.
Elf32_Phdr *PHT[EHT->e_phnum];
Elf32_Shdr *SHT[EHT->e_shnum];
while(EHT->e_phnum > i++){
PHT[i]=(Elf32_Phdr *)malloc(EHT->e_phentsize);
}
i=0;
while(EHT->e_shnum > i++){
SHT[i]=(Elf32_Shdr *)malloc(EHT->e_shentsize);
}
read_pht_eht(EHT,PHT,SHT,fd);
Bu 3 header bilgisi kullanılarak ile disk üzerinde text section xorlanır.
int xor_text(int fd,Elf32_Shdr *SHT[],Elf32_Ehdr *EHT){
.
.
while(EHT->e_shnum > i++){
if(EHT->e_entry >= SHT[i]->sh_addr && EHT->e_entry < SHT[i]->sh_addr + SHT[i]->sh_size ){
index=i; break;
}
inf->old_entry=EHT->e_entry;
inf->text_rva=SHT[index]->sh_addr;
inf->size=SHT[index]->sh_size;
yukarıdaki kod ile text sectionın Section Header Tablosu içerisinde nerede olduğu bulunur. Info structının ilgili üyeleri ilgili değerlerle setlenir.
lseek(fd,SHT[index]->sh_offset,SEEK_SET);
char *text_section=(char *)malloc(SHT[index]->sh_size);
read(fd,text_section,SHT[index]->sh_size);
i=0;
while(i++<SHT[index]->sh_size){
*(text_section+i)=*(text_section+i) ^ 0x12;
}
inf->xor_key=0x12;
lseek(fd,SHT[index]->sh_offset,SEEK_SET);
write(fd,text_section,SHT[index]->sh_size);
yukarıdaki kod parçası text sectionını 0x12 hexadecimal değeri ile xorlar.İstenilirse dinamik olarak xor key için bir değişken tanımlanarak dışarıdan key girdisi alınabilir.
void reconstruct_segments(int fd,int index,Elf32_Ehdr *EHT,Elf32_Phdr *PHT[],Elf32_Shdr *SHT[])
{
.
.
while(EHT->e_phnum > i){
if(PHT[i]->p_type==PT_LOAD && (PHT[i]->p_vaddr < SHT[index]-> sh_addr && PHT[i]->p_vaddr+PHT[i]->p_filesz > SHT[index]->sh_addr)){
PHT[i]->p_flags=PF_X + PF_R + PF_W;
i++;
continue;
}
if(PHT[i]->p_type==PT_LOAD)
index2last=i;
i++;
}
Yukarıdaki kod parçası text sectionın hangi segment içerisinde olduğunu bulduktan sonra o segmentin izinlerini yazılabilir, okunabilir,çalıştırılabilir olarak setlemektedir. Bunun nedeni Çalışma zamanında xorlanmış text sectionının tekrardan düzeltilmesi gerekecektir.
Ardından son LOAD segmentinin index numarası index2last değişkenine atılmaktadır.
inf->offset = PHT[index2last]->p_offset;
inf->endofsegment = PHT[index2last]->p_offset+PHT[index2last]->p_filesz;
inf->rva=PHT[index2last]->p_vaddr;
son Load segmentine ilişkin bilgiler info structı içerisine yerleştirilmektedir.
PHT[index2last]->p_flags=PF_X + PF_R + PF_W;
PHT[index2last]->p_filesz+=500;
PHT[index2last]->p_memsz+=500;
Son LOAD segmentinin disk üzerindeki dosya boyutunu ve memorydeki dosya boyutunu arttırıyoruz ve ayrıca segmentin izinlerini çalıştırılabilir, okunabilir , yazılabilir olarak setliyoruz.
Son LOAD segmentinin sonuna inject kod adını verdiğimiz bir kodu yerleştireceğiz. Bu kod text sectionı tekrar xorlayarak düzeltecektir.
Elf32_Addr inject_runtimecode(int fd,char *code){
int current_offset = inf->endofsegment + sizeof(struct info)+10;
int current_rvaddr = inf->rva +(current_offset-inf->offset);
current_offset ile inject kodunun ilk baytının disk üzerindeki offset bilgisini tutuyoruz.
current_rvaddr ile inject kodunun ilk baytının relative virtual addres değerini tutuyoruz.
inf->ep_distance=current_rvaddr-inf->old_entry+0x32;
inf->text_distance=current_rvaddr-inf->text_rva+0x32;
text sectionı düzelttikten sonra eski entrypoint adresine jump emri ile sıçrama yapacağımız için ep_distance ,text_distance ile inject kod ile text section adresi arasındaki uzaklık hesaplanmaktadır.
lseek(fd,current_offset,SEEK_SET);
write(fd,inf,sizeof(struct info));
lseek(fd,current_offset+sizeof(struct info)+5,SEEK_SET);
write(fd,code,70);
return current_rvaddr+sizeof(struct info)+5;
yukarıdaki kod parçası ile info structı ve inject kod son LOAD segmentine yerleştirilmiştir.
inject_runtimecode dönüş olarak bize yeni entrypoint adresini döndermektedir.
Elf32_Addr new_ep=inject_runtimecode(fd,inject_code);
EHT->e_entry=new_ep;
lseek(fd,0,SEEK_SET);
write(fd,EHT,sizeof(Elf32_Ehdr));
Son olarak elf header içerisinde entrypoint adresini değiştirerek , dosya belleğe yüklendikten sonra inject kodundan itibaren başlamasını sağlıyoruz.
Inject Code Design
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Son olarak Inject edilen koddan bahsederek yazıyı bitirebiliriz.
char *inject_code="\x60\x90\x90\x90\xe8\x2c\x00\x00\x00\x8b\x4d\xea\x89\xeb"
"\x2b\x5d\xe2\x8a\x13\x32\x55\xee\x88\x13\x49\x43\x83\xf9\x00\x0f"
"\x85\xee\xff\xff\xff\x8b\x4d\xe6\x83\xc1\x2c\xf7\xd9\x89\x4d\x28"
"\x61\x90\xe9\xef\xbe\xad\xde\x8b\x2c\x24\xc3";
yukarıdaki kod inject kodumuzun opcode halidir. Aşağıda bu kodun assembly hali ve memory düzeni görülmektedir.
/*
___INJECT__CODE__ :
0x0:old_entry
0x4 :offset
0x8 :endofsegment
0xc :rva
0x10:text_rva
0x14:text_distance
0x18:ep_distance
0x1c:size
0x20:xor_key
0x21:JUNK_BYTE
0x22:JUNK_BYTE
0x23:JUNK_BYTE
0x24:JUNK_BYTE
0x25:JUNK_BYTE
0x26:pusha
0x27:nop
0x28:nop
0x29:nop
0x2a:call 0x2c
0x2f:mov ecx,DWORD PTR[ebp-0x16]
0x32:mov ebx,ebp
0x34:sub ebx,DWORD PTR[ebp-0x1e]
0x37:mov dl,BYTE PTR[ebx]
0x39:xor dl,BYTE PTR [ebp-0x12]
0x3c:mov BYTE PTR [ebx],dl
0x3d:dec ecx
0x3e:inc ebx
0x3f:cmp cx,0x0
0x42:jnz -0x12
0x48:mov ecx,DWORD PTR [ebp-0x1a]
0x4b:add ecx,0x2c
0x4f:neg ecx
0x51:mov DWORD PTR [ebp+0x28],ecx
0x54:popa
0x55:nop
0x56:jmp 0xdeadbeef
0x5b:mov ebp,[esp]
0x5e:ret
0x5f:nop
________________________________________________________________________
*/
Inject kodunun 0.bayttan itibaren yüklendiğini varsayılmıştır. Bu durumda kodumuz 0x26.bayttan itibaren çalışmaya başlayacaktır.
Pusha emri ile registerların kendisi stacke itilmektedir. ELF dosyasının Startup aşamasında bazı registerlar kullanıldığı için registerların korunması gerekmektedir. Bu yüzden öncelikle tüm register içeriklerini stacke itmemiz gerekmektedir.
Yukarıda keyfi olarak bazı yerler JUNK_BYTE bazı yerlerde NOP emri ile doldurulmuştur. Bu emirleri değiştirmeniz durumunda inject kod ile text sectionı arasındaki uzaklık hesabını yeniden gözden geçirmeniz gerekecektir.
Call 0x2c emri ile bir sonraki adresin(0x2f adresi) adresi stacke itildikten sonra 0x5b adresine atlanacaktır. Burada mov ebp,[esp] ile 0x2f emrinin bellekteki adresi elde edilecektir. Bunu yapmamızın nedeni daha önce bahsettiğimiz gibi Position Independent Code yöntemini kullanmamız.
0x5e adresindeki ret instructionı ile tekrardan 0x2f adresine dönmekteyiz. Burada text sectionın size
bilgisi 0x2f adresinden -0x16 bayt gerideki info structında tutulduğundan dolayı 0x2f(ebp
içerisindeki adres 0x2f memorydeki adres değeri) değerinden 0x16 hexadecimal değeri çıkarılmıştır.
0x32:mov ebx,ebp
0x34:sub ebx,DWORD PTR[ebp-0x1e]
Bu emirler ile text sectionın başlangıc adresini hesaplıyoruz.
0x37:mov dl,BYTE PTR[ebx] // text section içerisideki kodu al
0x39:xor dl,BYTE PTR [ebp-0x12] // text section ile ebp-0x12 adresindeki değeri xorla. Ebp-0x12 xor_key değerinin tutulduğu adrestir.
0x3c:mov BYTE PTR [ebx],dl // xorlanan değeri ilgili adrese tekrar yaz.
0x3d:dec ecx // size değerini bir azalt
0x3e:inc ebx // adres değerini bir arttır
0x3f:cmp cx,0x0 // text sectionın tamamı xorlandımı ?
0x42:jnz -0x12 // hayırsa 0x37 adresine atlama gerçekleştir.
Yukarıdaki kod parçası bir döngü içerisinde text sectionını xorlamaktadır. Aynı xor keyi ile xorlama decryption işlemine karşılık gelmektedir. Neden tekrar xorladığımızı anlamadıysanız lütfen xor doğruluk tablosuna göz atınız.
0x48:mov ecx,DWORD PTR [ebp-0x1a]
0x4b:add ecx,0x2c
0x4f:neg ecx
0x51:mov DWORD PTR [ebp+0x28],ecx
0x54:popa
0x55:nop
0x56:jmp 0xdeadbeef
Yukarıda ecx registerına ep_distance atanmaktadır ardından bu değer 0x2c değeri ile toplanmaktadır. Bunun nedeni uzaklık hesaplaması yaparken entrypoint adresi ile mov ecx,[ebp-0x16] emrinin arasındaki uzaklığı hesaplamamızdır. Eğer jmp 0xdeadbeef emri ile entry point adresi arasında uzaklık hesabı yapmış olsaydık buna gerek kalmazdı.
Ayrıca neg ecx ile hesapladığımız değerin tümleyenini alarak negatif bir değere çeviriyoruz çünkü kullandığımız jmp emri operand değeri ile bir sonraki adresi toplayarak göreceli olarak bir atlama gerçekleştirecektir.
0x51:mov DWORD PTR [ebp+0x28],ecx
Bu emir çalıştığı zaman entrypoint adresi ile jump adresi arasındaki uzaklık jmp emrinin operand değerine yazılacaktır.
Popa emiri ile , Pusha emri ile tüm stacke ittiğimiz register değerlerini tekrardan registerlara yüklüyoruz. Burada tabi stacki korumamız gerektiğine dikkat ediniz.
son olarak 0x56 adresindeki jmp emri icra edilerek orijinal/eski entrypoint adresine sıçrama gerçekleşecektir.
Kodun tamamına ulaşmak için şuraya tıklayabilirsiniz.
umarım faydalı olmuştur.
EOF
Hiç yorum yok:
Yorum Gönder