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:
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:
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:
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 …