TSOC-CTF 2018 Oppagve 7
Inequation Group deltok og vant Telenor Security Operation Centre sin CTF.
I denne blogposten skal vi ta en titt på hvordan vi kan løse oppgave 7.
Oppgave 7
Klaus har lagret en fil ved navn «crackme1» på sitt hjemmeområde. Den spør etter et passord, kan du finne passordet?
Vi kjører programmet file mot filen og ser at det er en 64 bit elf altså en linux programfil som ikke har blitt stripped dette betyr at vi har detaljer om orignale funksjonnavn og slik i filen.
$file crackme1
crackme1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked ...[SNIPPED]..., not stripped
Vi åpner så filen i radare2 med -A flagget slik at den automatisk analyserer filen som en programfil vi kjører så afl komandoen for å få en liste over funksjonene i programmet
$r2 -A crackme1
...[SNIPPED]...
[0x000006a0]> afl
0x00000000 3 72 -> 73 sym.imp.__libc_start_main
0x00000618 3 23 sym._init
0x00000640 1 6 sym.imp.puts
0x00000650 1 6 sym.imp.__stack_chk_fail
0x00000660 1 6 sym.imp.printf
0x00000670 1 6 sym.imp.strcmp
0x00000680 1 6 sym.imp.__isoc99_scanf
0x00000690 1 6 sub.__cxa_finalize_690
0x000006a0 1 43 entry0
0x000006d0 4 50 -> 40 sym.deregister_tm_clones
0x00000710 4 66 -> 57 sym.register_tm_clones
0x00000760 4 49 sym.__do_global_dtors_aux
0x000007a0 1 10 entry1.init
0x000007aa 6 187 main
0x00000870 4 101 sym.__libc_csu_init
0x000008e0 1 2 sym.__libc_csu_fini
0x000008e4 1 9 sym._fini
Vi ser at main ganske så sikkert er den eneste ikke importerte funksjonen i programmet. Vi søker så til main med s komandoen og så printer vi ut disassemblyen med pdf kommandoen p for å printe d for å dissasemble og f for å spesifikkere at vi vil dissamblere en funksjon
[0x000006a0]> s main
[0x000007aa]> pdf
/ (fcn) main 187
| main ();
| ; var int local_54h @ rbp-0x54
| ; var int local_50h @ rbp-0x50
| ; var int local_48h @ rbp-0x48
| ; var int local_40h @ rbp-0x40
| ; var int local_30h @ rbp-0x30
| ; var int local_8h @ rbp-0x8
| ; DATA XREF from 0x000006bd (entry0)
| 0x000007aa 55 push rbp
| 0x000007ab 4889e5 mov rbp, rsp
| 0x000007ae 4883ec60 sub rsp, 0x60
| 0x000007b2 64488b042528. mov rax, qword fs:[0x28]
| 0x000007bb 488945f8 mov qword [local_8h], rax
| 0x000007bf 31c0 xor eax, eax
| 0x000007c1 48b8536c6170. movabs rax, 0x6361427070616c53
| 0x000007cb 48ba6f6e5061. movabs rdx, 0x7a69506161506e6f
| 0x000007d5 488945b0 mov qword [local_50h], rax
| 0x000007d9 488955b8 mov qword [local_48h], rdx
| 0x000007dd 48b87a615375. movabs rax, 0x7265677553617a
| 0x000007e7 488945c0 mov qword [local_40h], rax
| 0x000007eb 488d3d020100. lea rdi, qword str.Enter_Password: "Enter Password: "
| 0x000007f2 b800000000 mov eax, 0
| 0x000007f7 e864feffff call sym.imp.printf
| 0x000007fc 488d45d0 lea rax, qword [local_30h]
| 0x00000800 4889c6 mov rsi, rax
| 0x00000803 488d3dfb0000. lea rdi, qword [0x00000905] ; "%s"
| 0x0000080a b800000000 mov eax, 0
| 0x0000080f e86cfeffff call sym.imp.__isoc99_scanf
| 0x00000814 488d55b0 lea rdx, qword [local_50h]
| 0x00000818 488d45d0 lea rax, qword [local_30h]
| 0x0000081c 4889d6 mov rsi, rdx
| 0x0000081f 4889c7 mov rdi, rax
| 0x00000822 e849feffff call sym.imp.strcmp
| 0x00000827 8945ac mov dword [local_54h], eax
| 0x0000082a 837dac00 cmp dword [local_54h], 0
| ,=< 0x0000082e 750e jne 0x83e
| | 0x00000830 488d3dd10000. lea rdi, qword str.Success ; Success"
| | 0x00000837 e804feffff call sym.imp.puts
| ,==< 0x0000083c eb0c jmp 0x84a
| || ; JMP XREF from 0x0000082e (main)
| |`-> 0x0000083e 488d3dcb0000. lea rdi, qword str.Fail ; "Fail!"
| | 0x00000845 e8f6fdffff call sym.imp.puts
| | ; JMP XREF from 0x0000083c (main)
| `--> 0x0000084a b800000000 mov eax, 0
| 0x0000084f 488b4df8 mov rcx, qword [local_8h]
| 0x00000853 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x0000085c 7405 je 0x863
| | 0x0000085e e8edfdffff call sym.imp.__stack_chk_fail
| | ; JMP XREF from 0x0000085c (main)
| `-> 0x00000863 c9 leave
\ 0x00000864 c3 ret
Vi ser av dette at programmet aksepterer input fra standard inndata og at den skrivet dette til rbp-0x30, [local_30h] i radar utskriften.
| 0x000007fc 488d45d0 lea rax, qword [local_30h]
| 0x00000800 4889c6 mov rsi, rax
| 0x00000803 488d3dfb0000. lea rdi, qword [0x00000905] ; "%s"
| 0x0000080a b800000000 mov eax, 0
| 0x0000080f e86cfeffff call sym.imp.__isoc99_scanf
Dette vet vi siden RSI er det andre argumentet til funksjoner på grunn64 bit linux calling conventions dermed så kan vi forstå at dette under tilsvarer koden over
scanf(rdi,rsi);
siden RDI pointer til stringen "%s" og RSI pointer til rbp-0x30 (local_30) så kan vi gjette at RBP-0x30 er et array
og at disse 5 instruksjonene kan bli "oversatt" til scanf("%s",local_30);
Rett etter disse instruksjonene så har vi noe kode før strcmp som fører til logikk som printer ut Congratiulations om de to tekststrengene er like.
| 0x00000814 488d55b0 lea rdx, qword [local_50h]
| 0x00000818 488d45d0 lea rax, qword [local_30h]
| 0x0000081c 4889d6 mov rsi, rdx
| 0x0000081f 4889c7 mov rdi, rax
| 0x00000822 e849feffff call sym.imp.strcmp
| 0x00000827 8945ac mov dword [local_54h], eax
| 0x0000082a 837dac00 cmp dword [local_54h], 0
| 0x0000082e 750e jne 0x83e
| 0x00000830 488d3dd10000. lea rdi, qword str.Success ; Success"
| 0x00000837 e804feffff call sym.imp.puts
Vi ser at tekststrengen vi ga programmet blir lastet inn i rdi, det første argumentet i en funksjons call, og at denne blir sammenlignet med en annen string som er lagret i adressen rbp-0x50, [local_50] i radar utskriften. Vi kan “oversette” denne koden slik
char *rsi = local_50 ; // vi må anta at local_50 er en char *
char *rdi = input; // [Local_30]
if(stcmp(input,local_50)==0)
{
puts("Success");
}
// Jeg skippet swappen mellom registere før callen
Flagget må være hva som er lagret i local_50, vi kan finne hva som er lagret der enten ved å bruke statisk eller dynamisk analyse.
Statisk analyse
Vi ser at ved starten av programmet så blir local_50 satt til 0x6361427070616c53 dette nummeret består av asciiverdier, vis vi konverteren dette til asciiså får vi caBppalS denne tekstrengen er baklengs på grunn av Endianess. Rett etter dette så ser vi at noen andre lignende nummer blir plassert på stacken. Siden arrays fungerer baklengs i forhold til stacken så kan vi gjette at disse tilhører local_50
| 0x000007c1 48b8536c6170. movabs rax, 0x6361427070616c53
| 0x000007cb 48ba6f6e5061. movabs rdx, 0x7a69506161506e6f
| 0x000007d5 488945b0 mov qword [local_50h], rax
| 0x000007d9 488955b8 mov qword [local_48h], rdx
| 0x000007dd 48b87a615375. movabs rax, 0x7265677553617a
| 0x000007e7 488945c0 mov qword [local_40h], rax
Siden stacken vokser backlengs så kan vi anta at den første stelen av stringen
lagret baklengs er 0x6361427070616c53 den andre delen er 0x7a69506161506e6f og den
siste delen er 0x7265677553617a
Vi kan lett skrive et python script for å dekodifiserer dette
#!/usr/bin/python3
str = ""
str += bytes.fromhex('6361427070616c53').decode('ascii')[::-1]
str += bytes.fromhex('7a69506161506e6f').decode('ascii')[::-1]
str += bytes.fromhex('7265677553617a').decode('ascii')[::-1]
print(str)
Dette Spytter ut flagget 'SlappBaconPaaPizzaSuger'
Dynamisk analyse
Vi kan også se hva som ligger i rsi (det andre argumentet til funksjonen) når programmet caller strcmp dette er ganske så lett og vi kan gjøre dette med gdb. Vi starter med gdb -q [programfil], vi kjører så disassemble main kommandoen for å få disassemblyen av main slik at vi kan vite hvilket offset i main vi vil breake på. For å breake ved det offsettet skriver break *main+offset før vi kjører programet med r. Når vi når breakpointet så printer vi ut rsi med x/s $rsi, x for å spesifisere at vi vil printe ut en adresse /s for å printe ut dette som en string
gdb -q ./crackme1
Reading symbols from ./crackme1...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel // jeg kjører dette for å få disambly i intel syntax
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000000007aa <+0>: push rbp
0x00000000000007ab <+1>: mov rbp,rsp
0x00000000000007ae <+4>: sub rsp,0x60
...[SNIPP]...
0x0000000000000822 <+120>: call 0x670 <strcmp@plt>
0x0000000000000827 <+125>: mov DWORD PTR [rbp-0x54],eax
...[SNIPP]...
0x0000000000000863 <+185>: leave
0x0000000000000864 <+186>: ret
End of assembler dump.
(gdb) break *main+120
Breakpoint 1 at 0x822
(gdb) r
Starting program: /home/NSA_NOT_TODAY/TSOCCTF/crackme1
Enter Password: asd
Breakpoint 1, 0x0000555555554822 in main ()
(gdb) i r rsi
rsi 0x7fffffffe180 140737488347520
(gdb) x/s $rsi
0x7fffffffe180: "SlappBaconPaaPizzaSuger"
IDA
letteste måten å løse det hele er egentlig bare å bruke IDA PRO :), hvis man åpner filen i Ida PRO og søker til main så kan man bare trykke F5 og få IDA sin “oversettelse” av det hele.
int __cdecl main(int argc, const char **argv, const char** envp)
{
char s2[8]; // [rsp+10h] [rbp-50h]
char s1; // [rsp+30h] [rbp-30h]
unsigned __int64 v6; // [rsp+58h] [rbp-8h]
v6 = __readfsqword(0x28u);
strcpy(s2, "SlappBaconPaaPizzaSuger");
printf("Enter Password: ", argv);
__isoc99_scanf("%s", &s1);
if ( !strcmp(&s1, s2) )
puts("Success");
else
puts("Fail!");
return 0;
}
som vi kan se så har ida “oversatt” det hele ganske så bra.