- Published on
Savaş Oyunları: Format İpliklerinin Dönüşü Part II
- Authors
- Name
- Furkan Er
- @erfur_qwe
Table of contents
Önceki yazıda tw33tchainz programının altını üstüne getirdik. Disassembler'da incelediğimiz programın kullanıcıya gösterilmeyen ilginç prosedürler içerdiğini keşfettik. Programın çifte standart uyguladığını, admin isek debug modunu açabildiğimizi gördük. Peki nasıl admin olunur? Fonksiyonları hatırlayıp, daha detaylı inceleyip görelim.
Not: Önceki yazıdaki radare2'yi ssh üzerinden bize verilen sanal imaja bağlanıp kullanmıştım. Eski sürüm olduğu için birçok özellikten mahrum kaldım. Bu sefer tw33tchainz dosyasını indirip kendi makinamda analize devam edeceğim. radare2'yi mümkün olduğunca en son sürümde kullanmaya çalışın. Çok aktif bir geliştirme sürecinde olduğu için uygulamaya her sürümde önemli özellikler ekleniyor._
- print_banner
- gen_pass
- gen_user
- print_banner
- print_menu
- get_unum
print_banner()
Program çıktının renkli olması için printf ile escape sequence bastırdıktan sonra print_banner() fonksiyonunu çağırıyor. Bu fonksiyonda da program afişini bastırıyor.
Daha sonra gen_pass() fonksiyonu çağrılıyor.
gen_pass()
Bu fonksiyonda /dev/urandom'dan 16 byte okunup obj.secretpass adresine yazılıyor. İleride kullanmamız gerecek bir detaya benziyor.
Sırada gen_user() fonksiyonu var.
gen_user()
Fonksiyon ilk olarak bellekteki iki alana memset() ile belli byte değerleri yazıyor:
memset(0x804d0d0, '\xba', 0x10);
memset(obj.user, '\xcc', 0x10);
Daha sonra ekrana "Enter Username:" yazdırıp fgets() ile input alıyor:
Verdiğimiz input obj.user adresine yazılıyor. Ardından program "Enter Salt:" yazdıktan sonra bizden salt değerini istiyor:
Bu sefer girilen inputu da 0x804d0d0 adresine yazdıktan sonra local_18 adresi stack'e yazılıp hash() fonksiyonu çağrılıyor. "Generated Password:" mesajı ekrana bastırıldıyor ve hemen ardından local_18 adresi tekrar stack'e yazılıp print_pass() fonksiyonu çağrılıyor. Son olarak da obj.user ve 0x804d0d0 adresleri memset() ile tekrar dolduruluyor.
Şimdi hash() fonksiyonunu inceleyip programın bize ürettiği parolayı nasıl ürettiğini öğrenelim.
hash()
Oldukça basit bir fonksiyonla karşılaşıyoruz. local_4h adresinde bir sayaç değeri var ve sol alttaki bloğu 16 kere çalıştırıyor. Bu blokta aşağıdaki işlem gerçekleşiyor:
arg_8h = obj.user ^ (0x804d0d0 + obj.secretpass)
Bu işlem her byte için ayrı ayrı gerçekleşiyor. Sonuçta elde edilen parola print_pass() fonksiyonuna veriliyor.
print_pass()
print_pass() fonksiyonunda elde edilen parolanın dörder byte'lık bloklar halinde tersine çevrildiğini görüyoruz. Bu işlemden sonra parolamız hex halinde bastırılıyor.
Programın incelediğimiz yere kadar yaptıklarının çıktısı aşağıdaki gibi:
Enter'a bastıktan sonra program bizi ana menüye alıyor.
Sal admini artık!
Ana menüden erişebileceğimiz fonksiyonları da hatırlayalım:
- 0 getchar
- 1 do_tweet
- 2 view_chainz
- 3 maybe_admin
- 4 print_banner
- 5 print_exit
- 6 ???
Direkt en ilgi çeken fonksiyona girelim ve admin olmak için ne istiyormuş görelim:
Program bizden bir parola istiyor ama hangi parola olduğu belli değil. Programın kendi ürettiği parolayı denedim ancak işe yaramadı (O kadar kolay olsaydı bu kadar uğraşmazdık demi?). Kodları inceleyip durumu açıklığa kavuşturalım:
Yukarıdaki kodun C'de karşılığı şöyle:
memset(local_19h, '\x00', 0x11);
printf("Enter password: ");
fgets(local_19h, 0x11, stdin);
if(memcmp(local_19h, obj.secretpass, 0x10) == 0) {
puts("Authenticated");
obj.is_admin = 1;
} else {
puts("Nope");
obj.is_admin = 0;
}
Girdiğimiz parola obj.secretpass ile aynı ise is_admin değişkeni 1 olacak. obj.secretpass değeri /dev/urandom'dan okunan 16 byte'tan ibaretti. Peki bu değeri nasıl ele geçirebiliriz? hash() fonksiyonuki işleme tekrar bakarsak:
arg_8h = obj.user ^ (0x804d0d0 + obj.secretpass) /*
hash salt */
Eğer username ve salt değerlerini akıllıca girersek programın bize ürettiği parola üzerinden secretpass'i ele geçirebiliriz. Programın yazarı burada bize bir iyilik yapıp fgets() kullanmış. Bu fonksiyon (man fgets) inputu sadece newline('\n') ve EOF görünce sonlandırıyor. Yani hem username hem de salt değeri olarak "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" kullanabiliriz. Böylece:
hash[i] = 0 ^ (0 + secretpass[i]) = secretpass[i]
olacak ve secretpass'i tereyağından kıl çeker gibi okuyabileceğiz.
Alet çantası nerde?
Bahsettiğim işlemleri elle yapmak zor olmasa da ileride olayların karmaşıklaşacağını öngördüğümüz için işi araçlarla otomatize etmemiz gerekiyor.
"Bir tersine mühendis ancak kullandığı araçlar kadar güçlüdür." - erfur
Çantayı karıştırıp içinden python ve pwntools'u çıkaralım. pwntools ile programın girdi ve çıktılarını kolaylıkla kontrol edebiliyoruz. Öncelikle scripte bir giriş yapalım:
#!/usr/bin/python2
from pwn import *
binary = ELF("./tw33tchainz")
io = binary.process()
Elimizdeki programı yükleyip çalıştırmak için gerekli kod bu kadar. Bundan sonra program ile etkileşim kısmını hallediyor olacağız.
userstr = "\x00"*16
# Enter Username:
io.recv()
io.send(userstr)
saltstr = "\x00"*16
# Enter Salt:
io.recv()
io.send(saltstr)
Belirlediğimiz kullanıcı adı ve salt değerini verdikten sonra programın oluşturduğu parolayı parse etmemiz gerekiyor. Programın her çalıştığında çıktıyı aynı şekilde verdiğini bildiğimiz için işimiz kolay.
io.recvuntil("Generated Password:\n")
hashstr = io.recvline().strip()
log.info("Got hash: {}".format(hashstr))
io.sendline("\n")
Şu anda hashstr secretpass'in ters halini tutuyor. Küçük bir for döngüsüyle düzeltelim:
Örnek parola:
df5c94a02bdf1f6872b37b50ee5705a9
v v v v
[df5c94a0][2bdf1f68][72b37b50][ee5705a9]
v v v v
[a0945cdf][681fdf2b][507bb372][a90557ee]
v v v v
a0945cdf681fdf2b507bb372a90557ee
İşlemi python koduna çevirdiğimizde ise:
hashstr2 = []
for i in range(0, len(hashstr), 8):
hashstr2.append(int(hashstr[i+6:i+8], 16))
hashstr2.append(int(hashstr[i+4:i+6], 16))
hashstr2.append(int(hashstr[i+2:i+4], 16))
hashstr2.append(int(hashstr[i:i+2], 16))
log.info("Fixed hash: {}".format("".join([hex(i)[2:] for i in hashstr2])))
secretpass = ""
for i in range(16):
secretpass += chr(((hashstr2[i]^ord(userstr[i])) - ord(saltstr[i]))
& 0xff)
log.info("secretpass: {}".format(secretpass))
secretpass hazır olduğuna göre menüye girip maybe_admin() fonksiyonuna tekrar kafa tutabiliriz.
log.info(io.recv())
io.sendline("3") #maybe_admin()
io.recv()
io.sendline(secretpass)
io.interactive()
Scriptimiz programa secretpassi verdikten sonra kontrolü bize bırakacak. Böylece programı admin olarak istediğimiz gibi kullanabileceğiz. Şimdi scripti denemenin vakti geldi.
$ ./solve.py
Admin olmayı başardık ve menüde yeni bir girdi belirdi: "6: Turn debug mode on". Debug modunu açıp programı kullandığımızda "View chainz" menüsünde attığımız tweetlerin hafızadaki adreslerini görebiliyoruz:
Bu adresleri exploit geliştirme aşamasında kullanmamız gerekebilir.
İkinci yazının sonuna geldik. Üçüncü ve (umuyorum ki) son yazıda programdaki zafiyetleri inceleyip program üzerinden kod çal̙̣̘̕ı͏̻̜ş̡̻͇͚t͉͓͉̹͙ı̦̲̲͝r̙m̩̝͍ͅa͚̟̖y͍̲͇̞̭ı̶͔̰͓̫ d̨̡̹̲̹͘é͈̻̱̘͙͖̩̝͈͝n̛͜͏̷͈͓̟̪̰̬͓̞ e̛̬̻̝͚̳͖̰͉͚̟͡y͈̻͖͎̯̮̘̦̤͙͓͍͇͘͘̕͡e̵͔̭̺̙͈͎̪̤͇̯̦̞͞ͅc̸̢̧̺̰͕̜̱̪̞͎͈͉̘̤̣̠ͅͅe̵̶̝͖̰͙̤̰̲͓͜͞ğ̶̨̹̠͕̖͟͜͠��
Segmentation fault (core dumped)