Encrypter - Decrpyter
Encrypter Dosyası
Encrypter dosyası çalıştırılabilir bir ELF uygulamasıdır. Bu uygulamanın mantığı bir şifreleme fonksiyonunu AES-CBC olarak verilen bir dosyası şifrelediği ve bu dosyanın
tekrardan çözümlenmesi için gerekli olan tersine mühendislik işlemleri ile birlikte uygulamanın kırılarak şifrelenmiş dosyaları geri kurtarmaktır.
Burada üzerinde duracağımız en önemli nokta ise KEY - IV değerlerini elde edibilmemizdir. Bunları elde ettiğimizde şifrelenmiş dosyaları geri Decrypt işlemi yaparak kurtarabiliriz.
Ayrıntılı olarak bahsedeceğimiz bu çözümde uygulamayı ayrıntılı olarak analiz edeceğiz.
İlk Çalıştırma
Komutu verildiğinde bize bir dosya şifreleme işlemi yapılmaktadır.
iVar1 = strcmp((char *)param_2[1],"encrypt");
if (iVar1 == 0) {
uVar3 = encrypt_file("flag.txt","flag.enc",(uchar *)&local_38,(uchar *)&local_48);
if ((int)uVar3 == 0) {
puts("Encrypt failed");
uVar2 = 3;
Bu işlem burada gerçekleştirilmektedir. Ghidra ile uygulamayı incelediğimizda C kodunda strcmp
fonksiyonu param_2 [1] ile parametre kontrolü yapmaktadır.
Eğer “encrypt” değerine eşit bir veri girildiyse şifreleme işlemi başlamaktadır. Burada yapılan şifreleme işlemini derinlemesine incelemeden önce değişkenlerimiz
local_38 = KEY
ve &local_48 = IV
dir. Burada bir simetrik şifreleme işlemi yapılıyor diyebiliriz çünkü elimizde iki adet bir değişken var ve bu değişken değerlerine göre
şifreleme işlemi yapılmaktadır. Flag.txt dosyasını alıp flag.enc
adıyla bir dosya yaratmaktadır. Burada ki amacımız bu değerlere ulaşıp işlemi tersine çevirmektir.
encrypt_file() Fonksiyonunun İncelenmesi
Bu fonksiyon incelediğinde içerisinde do_crypto
adında bir fonksiyon görmekteyiz bunu detaylı inceleyerek yani bu fonksiyona atlama yaparak içeriğini incelediğimizde
ne tür bir şifreleme tekniği kullandığını görebiliriz.
else {
uVar1 = do_crypto(__stream,__stream_00,param_3,param_4,1);
fclose(__stream);
fclose(__stream_00);
uVar1 = uVar1 & 0xffffffff;
}
do_crypto() Fonksiyonunun İncelenmesi
Fonksiyonu incelediğimizde uzun bir kod bloğu bizi karşılıyor ama bir aralık dikkatimizi çekiyor ve burada ise AES-CBC-256 şifrelemesi olduğunu görmekteyiz.
else {
local_840 = EVP_aes_256_cbc();
if (param_5 == 0) {
iVar1 = EVP_DecryptInit_ex(local_848,local_840,(ENGINE *)0x0,param_3,param_4);
if (iVar1 != 1) {
handle_errors();
}
}
EVP_aes_256_cbc()
fonksiyonu bize KEY ve IV değerleri ile bir şifreleme yapıldığının ipucunu vermektedir.
encrypt_file("flag.txt","flag.enc",(uchar *)&local_38,(uchar *)&local_48);
Burada bulunan Local_38
ve Local_48
KEY - IV İkilisi olduğunu böylelikte öğrenmiş olduk.
Fakat bu öğrendiğimiz sadece bir şifreleme yöntemi ve bu değerlere nasıl atama yapılıyor ve ne tür bir işlem akışı var bunu çözmemiz gerekmektedir.
Kod Bloklarının İncelenmesi - &local_48
Detaylıca incelediğimizde görüyoruz ki “1337” stringini local_48’in başladığı adrese kopyalar.
0x10
= 16 byte. Yani 16 byte’lık bir alana “1337” yazılır.
IV (16 byte): '1', '3', '3', '7', '\0', '\0'
, … (Toplam 16 byte) 1337’den geriye kalan değerlere ise \x00
Bytelar ile doldurulur.
Burada dikkatimiz bir alan daha çekiyor o ise call_embedded_shellcode()
fonksiyonu bu ise &local_38
Pointer değişkeni ile alakalı bir durumdur.
local_48 = 0;
local_40 = 0;
strncpy((char *)&local_48,"1337",0x10);
local_38 = 0;
local_30 = 0;
local_28 = 0;
local_20 = 0;
uVar2 = call_embedded_shellcode(&local_38,0x20);
if ((int)uVar2 == 0) {
fwrite("Failed to produce key via shellcode\n",1,0x24,stderr);
uVar2 = 2;
}
Kod Bloklarının İncelenmesi - &local_38
call_embedded_shellcode()
fonksiyonu incelendiğinde $rbp ‘ ye erişimi &Local_38 değişkeni üzerinden gerçekleşmektedir.
0x20
Hexadecimal değeri ise &local_38
değerinin 32 Byte olduğu anlaşılmaktadır.
Bu iki değişken yapısını incelerken $rbp - 0x48
ve $rbp - 0x38
olarak ele alırsak daha detaylı bir inceleme gerçekleştirebiliriz.
Kod Bloklarının İncelenmesi - Flag
Shellcode &local_38
KEY (Anahtar) ile doldurulduktan sonra ise &local_48
IV (Initialization Vector) ile şifreleme işlemi yapılmaktır.
Flag.txt adında bir dosyanın yerel dizinde bulunması gerekmektedir yani şifreleme yapmadan önce dosyamızın hazır olması gerekmektedir.
./encrypter encrypt
komutu yazıldığında ise Flag.enc
olarak dosya yaratılmaktadır.
if ((int)uVar2 == 0) {
fwrite("Failed to produce key via shellcode\n",1,0x24,stderr);
uVar2 = 2;
}
else {
iVar1 = strcmp((char *)param_2[1],"encrypt");
if (iVar1 == 0) {
uVar3 = encrypt_file("flag.txt","flag.enc",(uchar *)&local_38,(uchar *)&local_48);
if ((int)uVar3 == 0) {
puts("Encrypt failed");
uVar2 = 3;
}
else {
puts("Encrypted -> flag.enc");
uVar2 = 0;
}
}
else {
uVar2 = 0;
Dinamik Analiz
Ltrace ile yaptığımız dinamik analizde programın akışı aşağıdaki gibidir ayrıca detaylıca inceleyeceğiz. Burada aslında bir başka değinmek istediğimiz
nokta ise programların inceme aşamasında daha detalıca nasıl anlamlandırabiliriz bunu belirtmekteyim.
(1) strncpy(0x7fffad711250, "1337", 16) = 0x7fffad711250
(2) sysconf(_SC_PAGE_SIZE) = 4096
(3) mmap(nil, 4096, 0b111, 0x22, -1, 0) = 0x7f87983e2000
(4) memcpy(0x7f87983e2000, "\306G\0t\306G\001h\306G\0021\306G\003_\306G\0041\306G\005s\306G\006_\306G\at"..., 129) = 0x7f87983e2000
(5) munmap(0x7f87983e2000, 4096) = 0
(6) strcmp("encrypt", "encrypt") = 0
(7) fopen("flag.txt", "rb") = 0x56503376b2a0
(8) fopen("flag.enc", "wb") = 0x56503376b480
(9) EVP_CIPHER_CTX_new(0x56503376b2a0, 0x56503376b480, 0x7fffad711260, 0x7fffad711250) = 0x56503376b660
(10) EVP_aes_256_cbc(0, 0, 0, 0) = 0x7f87981b9bc0
(11) EVP_EncryptInit_ex(0x56503376b660, 0x7f87981b9bc0, 0, 0x7fffad711260) = 1
(12) fread(0x7fffad7109a0, 1, 1024, 0x56503376b2a0) = 19
(13) EVP_EncryptUpdate(0x56503376b660, 0x7fffad710da0, 0x7fffad710988, 0x7fffad7109a0) = 1
(14) fwrite("j\304g\304\003\216\257\316uiN\257\350\323\325\006\001", 1, 16, 0x56503376b480) = 16
(15) fread(0x7fffad7109a0, 1, 1024, 0x56503376b2a0) = 0
(16) EVP_EncryptFinal_ex(0x56503376b660, 0x7fffad710da0, 0x7fffad710988, 0x7fffad710da0) = 1
(17) fwrite("\331\177\323\320\0018\234\036\361\222\342d\3358\265-\001", 1, 16, 0x56503376b480) = 16
(18) EVP_CIPHER_CTX_free(0x56503376b660, 0x7fffad710da0, 0, 0x56503376b480) = 1
(19) fclose(0x56503376b2a0) = 0
(20) fclose(0x56503376b480) = 0
(21) puts("Encrypted -> flag.enc"Encrypted -> flag.enc
Burada incelediğimizde (1)
kısmında strncpy ile 1337+\x00
kalan Bytelar doldurulur.
2 ve 3
kısımda ise Hafıza atama işlemleri yapılmaktadır.
memcpy()
bölümünde yani 4.kısımda ise Shellcode hafızada çalıştırılacak yere kopyalanır.
7 ve 8
bölümünde ise flag.txt
dosyasımız açılmaktadır aynı zamanda Flag.enc
yazılabilir modda açılmaktadır.
10
bölümde ise EVP_aes_256_cbc
bize şifreleme tipinin ayrıca detayını vermektedir.
Kalan bölümlerde ise artık şifreleme işleminin tamamlanması yer almaktadır.
Debug
gdb -q ./encrypter -ex 'b call_embedded_shellcode' -ex 'run encrypt' -ex 'finish' -ex 'printf "RBP = %p\n", $rbp' -ex 'x/32bx $rbp-0x38' -ex 'x/16bx $rbp-0x48' -ex 'quit'
komutuyla uygulamayı debug ediyoruz ve call_embedded_shellcode()
noktasına bir BreakPoint koyuyoruz. Shellcode bölümüne geldiğimizde program akışı duracaktır.
run encrypt
ile ise argv[1] olarak bir argüman iletiyoruz. Finish komutu ile ise Fonksiyondan çıkış yaptıktan sonra bitmektedir.
$rbp: “Base Pointer” , main fonksiyonunun stack ( yığın ) çerçevesinin adresini tutar.
-ex 'x/32bx $rbp-0x38' -ex 'x/16bx $rbp-0x48'
Burada ise 32 ve 16 Bytelik 38 ve 48 Local değişkenlerin $RBP değerlerine ulaşmak için yazıyoruz.
Çalıştığında ise aşağıdaki gibi veriler gelmiştir ;
RBP = 0x7fffffffdce0
0x7fffffffdca8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffdcb0: 0x74 0x68 0x31 0x5f 0x31 0x73 0x5f 0x74
0x7fffffffdcb8: 0x68 0x33 0x5f 0x76 0x61 0x6c 0x75 0x33
0x7fffffffdcc0: 0x5f 0x30 0x66 0x5f 0x6b 0x33 0x79 0x00
0x7fffffffdc98: 0xde 0x60 0x55 0x55 0x55 0x55 0x00 0x00
0x7fffffffdca0: 0x31 0x33 0x33 0x37 0x00 0x00 0x00 0x00
Çözümleme yapıldığında ise
0x7fffffffdca8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
→ ASCII: "\x00\x00\x00\x00\x00\x00\x00\x00" (tamamen null baytlar — görünür karakter yok)
0x7fffffffdcb0: 0x74 0x68 0x31 0x5f 0x31 0x73 0x5f 0x74
→ ASCII: "t h 1 _ 1 s _ t" → birleştirilmiş: "th1_1s_t"
0x7fffffffdcb8: 0x68 0x33 0x5f 0x76 0x61 0x6c 0x75 0x33
→ ASCII: "h 3 _ v a l u 3" → birleştirilmiş: "h3_valu3"
0x7fffffffdcc0: 0x5f 0x30 0x66 0x5f 0x6b 0x33 0x79 0x00
→ ASCII: "_ 0 f _ k 3 y \\0" → birleştirilmiş: "_0f_k3y" (sonunda null terminator)
0x7fffffffdc98: 0xde 0x60 0x55 0x55 0x55 0x55 0x00 0x00
0x7fffffffdca0: 0x31 0x33 0x33 0x37 0x00 0x00 0x00 0x00
→ ASCII: "1 3 3 7 \\0 \\0 \\0 \\0" → birleştirilmiş: "1337"
Artık elimizde bir KEY ve IV değeri bulunmaktadır bundan sonrası basittir.
Bir Python scripti ile çözümlemeyi gerçekleştirebiliriz.
Çözümleme
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# 1. ADIM: GDB'den bulduğumuz anahtar
# 'th1_1s_th3_valu3_0f_k3y' string'ini 32 byte'a \x00 ile tamamlıyoruz.
KEY = b'th1_1s_th3_valu3_0f_k3y'.ljust(32, b'\x00')
# 2. ADIM: Kodda gördüğümüz IV
# '1337' string'ini 16 byte'a \x00 ile tamamlıyoruz.
IV = b'1337'.ljust(16, b'\x00')
try:
with open('flag.enc', 'rb') as f:
ciphertext = f.read()
# AES-CBC modunda bir cipher nesnesi oluştur
cipher = AES.new(KEY, AES.MODE_CBC, IV)
# Şifreli veriyi çöz ve padding'i kaldır
decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size)
# Sonucu ekrana yazdır
print("------ AH YALAN DUNYA - PWNED ------")
print(decrypted_data.decode('utf-8'))
print("----------------------------")
except Exception as e:
print(f"Hata oluştu: {e}")
if "Padding" in str(e):
print("HATA: Anahtar (KEY) veya IV yanlış. 'Incorrect padding' hatası aldınız.")
elif "key size" in str(e):
print(f"HATA: Anahtar tam olarak 32 byte değil. Boyut: {len(KEY)} byte.")
Ayrıca Dosyayı indirmek için > Encrypter İndirmek İçin <
Algoritma
