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 Net Writeup

Protostar Net Writeup

Tekrardan merhabalar. Protostar serimize kaldığımız yerden devam ediyoruz. Bu yazımda protostar daki net sorularını yapmaya çalışacağız.

NOT: Bu sorularda temel socket bilgisine sahip olmak gerekiyor. Eğer socket hakkında pek bilginiz yoksa sizi şuraya alalım: https://realpython.com/python-sockets/ . Bu uyarıyı yaptıktan sonra devam ediyorum.

Net 0

İlk örnek ile başlayalım. Soru açıklaması şu şekilde.

This level takes a look at converting strings to little endian integers.

Bu soruda string bir ifadeyi integer bir değere dönüştürmemiz gerekiyor. Kaynak koddan incelemeye devam edelim.

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

#define NAME "net0"
#define UID 999
#define GID 999
#define PORT 2999

void run()
{
  unsigned int i;
  unsigned int wanted;

  wanted = random();

  printf("Please send '%d' as a little endian 32bit int\n", wanted);

  if(fread(&i, sizeof(i), 1, stdin) == NULL) {
      errx(1, ":(\n");
  }

  if(i == wanted) {
      printf("Thank you sir/madam\n");
  } else {
      printf("I'm sorry, you sent %d instead\n", i);
  }
}

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);

  /* Don't do this :> */
  srandom(time(NULL));

  run();
}

Kaydan kodda arka planda bir işlem başlattığını görüyoruz. Bu işlem bize random bir sayı üretip ekrana basıyor. Ardından ise bu sayıyı little endian olarak istiyor. ps aux ile arka plan işlemlerine baktığımızda şunları görüyoruz.

user@protostar:~$ ps aux | grep net
root         9  0.0  0.0      0     0 ?        S    14:45   0:00 [netns]
999       1384  0.0  0.0   1532   272 ?        Ss   14:45   0:00 /opt/protostar/bin/net0
998       1386  0.0  0.0   1532   272 ?        Ss   14:45   0:00 /opt/protostar/bin/net1
997       1388  0.0  0.0   1532   272 ?        Ss   14:45   0:00 /opt/protostar/bin/net2
996       1390  0.0  0.0   1536   272 ?        Ss   14:45   0:00 /opt/protostar/bin/net3
user      1559  0.0  0.0   3272   644 pts/0    S+   16:32   0:00 grep net

Yani net sorularının tamamı arka planda çalışıyor. Pekala nc ile bağlanmayı deneyelim. net0 2999 portunda çalışıyordu. O zaman şu şekilde bağlanabiliriz.

user@protostar:~$ nc 127.0.0.1 2999
Please send '252296720' as a little endian 32bit int
asdf
I'm sorry, you sent 1717859169 instead

252296720 ifadesini little endian olarak istiyor. Bu sayı her bağlandığımızda değişecektir. Şimdi bu işi yapmak için ufak bir python scripti hazırlayalım. Öncelikle gerekli porta bağlanıyorum.

import socket
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2999)) # bağlan

Buradaki AF_INET IPv4 kullanacağımızı bilirtiyor. SOCK_STREAM ise TCP protokolünü kullanacağımızı. Bu ayarlar ile socket oluşturup bağlanıyoruz. Bağlantıdan sonra program tarafından yazdırılan satırı recv fonksiyonu ile okuyabiliriz.

a = s.recv(1024)
print "[+] " + a

Bu noktada program tarafından rastgele oluşturulan sayıyı yakalamamız gerekiyor. Bu değer bizim okuduğumuz satırda belirli karakterler arasında. Biraz incelemeyle 13-22 karakterler arasındaki ifadenin bizim işlem yapmamız gereken sayı olduğunu görebilirsiniz.

number = a[13:22]
num_hex = hex(int(number))
print num_hex

Sayıyı yukarıdaki gibi aldıktan sonra bu sayıyı little endian olarak tekrardan göndermemiz gerekecek. Hatırlarsanız struct.pack ile adresleri little endian karşılığıda dönüştürebiliyorduk. Burada da aynısını yapabiliriz.

struct.pack("I", int(number))

Bu satırdaki “I” little endian olduğunu gösteriyor.

Şimdi ise bu ifadeyi tekrar göndermek kalıyor. Ardından cevabı okuyacağız.

s.send(struct.pack("I", int(number)))
print s.recv(1024)
s.close()

send metodu ile gönderme işlemini yapıyoruz. Ardından recv ile sunucunun bize gönderdiği cevabı okuyup socketi kapatıyoruz.

Full script şu şekilde:

import socket
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2999))

a = s.recv(1024)
print "[+] " + a
number = a[13:22]
num_hex = hex(int(number))
print num_hex

s.send(struct.pack("I", int(number)))
print s.recv(1024)
s.close()

Ve çalıştıralım:

net0

Net 1

Bu soruda ise açıklama şu şekilde:

This level tests the ability to convert binary integers into ascii representation.

Binary integer bir sayıyı ascii karşılığına dönüştürmemiz gerekiyor.

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

#define NAME "net1"
#define UID 998
#define GID 998
#define PORT 2998

void run()
{
  char buf[12];
  char fub[12];
  char *q;

  unsigned int wanted;

  wanted = random();

  sprintf(fub, "%d", wanted);

  if(write(0, &wanted, sizeof(wanted)) != sizeof(wanted)) {
      errx(1, ":(\n");
  }

  if(fgets(buf, sizeof(buf)-1, stdin) == NULL) {
      errx(1, ":(\n");
  }

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

  if(strcmp(fub, buf) == 0) {
      printf("you correctly sent the data\n");
  } else {
      printf("you didn't send the data properly\n");
  }
}

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);

  /* Don't do this :> */
  srandom(time(NULL));

  run();
}

nc ile gerekli porta bağlandığımızda binary bir şeyler gönderdiğini görebilirsiniz. O zaman bu binary veriyi alıp dönüştürme işlemini uyguladıktan sonra tekrar geri dönermemiz gerekiyor.

Öncelikle gerekli porta socket ile bağlanıyorum.

import socket
from struct import pack, unpack

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2998))

Ardından program tarafından gelen veriyi okuyorum.

num = s.recv(1024)
print "[+] " + num

Şimdi unpack fonksiyonu ile bu veriyi integer karşılışıgö dönüştürelim.

bin = unpack("<I", num)[0]
print bin

Ardından sonucu programa gönderiyoruz.

s.send(str(bin))
print s.recv(1024)

Full script şu şekilde:

import socket
from struct import pack, unpack

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2998))

num = s.recv(1024)
print "[+] " + num

bin = unpack("<I", num)[0]
print bin

s.send(str(bin))
print s.recv(1024)

Ve çalıştıralım:

net1

Net 2

Bu soruda ise 4 tane sayıyı toplamamızı istiyor.

This code tests the ability to add up 4 unsigned 32-bit integers. Hint: Keep in mind that it wraps.

Kaynak kod şu şekilde:

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

#define NAME "net2"
#define UID 997
#define GID 997
#define PORT 2997

void run()
{
  unsigned int quad[4];
  int i;
  unsigned int result, wanted;

  result = 0;
  for(i = 0; i < 4; i++) {
      quad[i] = random();
      result += quad[i];

      if(write(0, &(quad[i]), sizeof(result)) != sizeof(result)) {
          errx(1, ":(\n");
      }
  }

  if(read(0, &wanted, sizeof(result)) != sizeof(result)) {
      errx(1, ":<\n");
  }


  if(result == wanted) {
      printf("you added them correctly\n");
  } else {
      printf("sorry, try again. invalid\n");
  }
}

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);

  /* Don't do this :> */
  srandom(time(NULL));

  run();
}

Sırayla 4 sayı okumamız gerekiyor. Bir integer 32 bit yani 4 byte. Bu durumda şu şekilde 4 sayının tamamını okuyabiliriz.

a1 = s.recv(4)
a2 = s.recv(4)
a3 = s.recv(4)
a4 = s.recv(4)

Ardından bu sayıları integera gönüştürüp toplama işlemini yapalım.

aa1 = unpack("<I", a1)[0]
aa2 = unpack("<I", a2)[0]
aa3 = unpack("<I", a3)[0]
aa4 = unpack("<I", a4)[0]

top = aa1 + aa2 + aa3 + aa4

Şimdi ise sonucu gönderelim:

s.send(pack("<I", top))
print s.recv(1024)

Full exploit kodu şu şekilde:

import socket
from struct import pack, unpack
import binascii

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 2997))

a1 = s.recv(4)
a2 = s.recv(4)
a3 = s.recv(4)
a4 = s.recv(4)

aa1 = unpack("<I", a1)[0]
aa2 = unpack("<I", a2)[0]
aa3 = unpack("<I", a3)[0]
aa4 = unpack("<I", a4)[0]

top = aa1 + aa2 + aa3 + aa4
s.send(pack("<I", top))
print s.recv(1024)

Ve çalıştıralım:

net2

Net 3

Bu soruda kaynak kod şu şekilde.

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

#define NAME "net3"
#define UID 996
#define GID 996
#define PORT 2996

/*
 * Extract a null terminated string from the buffer 
*/

int get_string(char **result, unsigned char *buffer, u_int16_t len)
{
  unsigned char byte;

  byte = *buffer;

  if(byte > len) errx(1, "badly formed packet");
  *result = malloc(byte);
  strcpy(*result, buffer + 1);

  return byte + 1;
}

/*
 * Check to see if we can log into the host
 */

int login(unsigned char *buffer, u_int16_t len)
{
  char *resource, *username, *password;
  int deduct;
  int success;

  if(len < 3) errx(1, "invalid login packet length");

  resource = username = password = NULL;

  deduct = get_string(&resource, buffer, len);
  deduct += get_string(&username, buffer+deduct, len-deduct);
  deduct += get_string(&password, buffer+deduct, len-deduct);

  success = 0;
  success |= strcmp(resource, "net3");
  success |= strcmp(username, "awesomesauce");
  success |= strcmp(password, "password");

  free(resource);
  free(username);
  free(password);

  return ! success;
}

void send_string(int fd, unsigned char byte, char *string)
{
  struct iovec v[3];
  u_int16_t len;
  int expected;

  len = ntohs(1 + strlen(string));

  v[0].iov_base = &len;
  v[0].iov_len = sizeof(len);
  
  v[1].iov_base = &byte;
  v[1].iov_len = 1;

  v[2].iov_base = string;
  v[2].iov_len = strlen(string);

  expected = sizeof(len) + 1 + strlen(string);

  if(writev(fd, v, 3) != expected) errx(1, "failed to write correct amount of bytes");
  
}

void run(int fd)
{
  u_int16_t len;
  unsigned char *buffer;
  int loggedin;

  while(1) {
    nread(fd, &len, sizeof(len));
    len = ntohs(len);
    buffer = malloc(len);

    if(! buffer) errx(1, "malloc failure for %d bytes", len);

    nread(fd, buffer, len);

    switch(buffer[0]) {
      case 23: 
        loggedin = login(buffer + 1, len - 1);
        send_string(fd, 33, loggedin ? "successful" : "failed");
        break;
      
      default:
        send_string(fd, 58, "what you talkin about willis?");
        break;
    }
  }
}

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);

  /* Don't do this :> */
  srandom(time(NULL));

  run(fd);
}

Arka planda çalışan servis len isminde bir değişkene network üzerinden veri yazıyor. Bu daha sonra göndereceğimiz verinin boyutu olacak. Ardından malloc ile buffer değişkeni için bellekten yer ayırtıyor.

Bir kaç sonraki satırda nread(fd, buffer, len); ise network üzerinden veri okuyor ve bu veriyi buffer değişkenine yazıyor. Şu anda buffer değişkeninde yazdığımız veri ve len değişkeninde ise verimizin uzunluğu tutuluyor.

Bu noktadan sonra buffer değişkeninin ilk karakterini kontrol ediyor. Eğer bu değer 23 e eşitse o zaman login işlemine başlıyor.

Login işlemi ise şu şekilde çalışıyor. Öncelikle gönderdiğimiz servis, username ve parolayı gerekli şekilde ayırıyor. Ayırma işleminden sonra ise strcmp fonksiyonu ile gerekli kontrol işlemini tamamlıyor.

Burada get_string fonksiyonu ise şu şekilde çalışıyor. Öncelikle alınan verinin ilk karakterini sayı şeklinde okuyor. Ardıdan bu sayı kadar bellekten alan ayırıyor ve result değişkenine kopyalama yapıyor.

Bu noktada biz öncelikle login için ne kadar uzunlukta veri göndereceğimizi sunucuya iletiyoruz. Ardından login paketini göndermemiz gerekiyor. Login paketinde ise servis, kullanıcı adı ve parola bulunacak.

Full exploit kodu şu şekilde:

from socket import *
from struct import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2996))

login = "\x17" + "\x05net3\x00" + "\x0dawesomesauce\x00" + "\x0apassword\x00"

s.send(pack("!H", len(login)))
s.send(login)

print s.recv(1024)
s.close()

Çalıştıralım:

root@protostar:/tmp# python net3.py 

!successful

EOF

Bu şekilde tüm soruları tamamlamış olduk.

Bu yazımında sonuna geldik. Umarım faydalı olmuştur. Daha sonra görüşmek üzere …

all tags