Executable : https://repo.zenk-security.com/hackingweek2014_ctf/crackme-05
$ file crackme-05 crackme-05: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), for GNU/Linux 2.6.32, statically linked, stripped
Le crackme 5 est un ELF 64 bits, compilé en static, strippé. Nous n'aurons pas cette fois les symboles pour nous aider.
Si on lance le binaire dans gdb :
$ gdb ./crackme-05 gdb$ r Starting program: /home/fab/challenge/hackingweek2014/crackme-05 Enter password please : test Wrong password [Inferior 1 (process 2799) exited normally] -----------------------------------------------------------------------------------------------------------------------[regs] RAX:Error while running hook_stop: No registers. gdb$
On note la chaine ”[Inferior 1 (process 2799) exited normally]”, nous indiquant que le binaire a lui même lancé un process fils.
Si l'on tente de stracer le binaire, on a l'output suivant :
$ strace -f ./crackme-05 execve("./crackme-05", ["./crackme-05"], [/* 60 vars */]) = 0 uname({sys="Linux", node="killerwhale", ...}) = 0 brk(0) = 0x15b1000 brk(0x15b21c0) = 0x15b21c0 arch_prctl(ARCH_SET_FS, 0x15b1880) = 0 readlink("/proc/self/exe", "/home/fab/challenge/hackingweek2"..., 4096) = 46 brk(0x15d31c0) = 0x15d31c0 brk(0x15d4000) = 0x15d4000 mprotect(0x400000, 1244143, PROT_READ|PROT_WRITE|PROT_EXEC) = 0 clone(Process 2745 attached child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x15b1b50) = 2745 [pid 2745] tgkill(2745, 2745, SIGSTOP <unfinished ...> [pid 2744] fstat(1, <unfinished ...> [pid 2745] <... tgkill resumed> ) = 0 [pid 2744] <... fstat resumed> {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 9), ...}) = 0 [pid 2745] --- SIGSTOP {si_signo=SIGSTOP, si_code=SI_TKILL, si_pid=2745, si_uid=1000} --- [pid 2744] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...> [pid 2745] --- stopped by SIGSTOP --- [pid 2744] <... mmap resumed> ) = 0x7f2cf99a1000 [pid 2744] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_STOPPED, si_pid=2745, si_status=SIGSTOP, si_utime=0, si_stime=0} --- [pid 2744] fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 9), ...}) = 0 [pid 2744] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2cf99a0000 [pid 2744] write(1, "Enter password please : ", 24Enter password please : ) = 24 [pid 2744] read(0, <unfinished ...> [pid 2745] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} --- [pid 2745] +++ killed by SIGSEGV +++ <... read resumed> 0x7f2cf99a0000, 1024) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=2745, si_status=SIGSEGV, si_utime=0, si_stime=0} --- read(0, test "test\n", 1024) = 5 mmap(NULL, 1245184, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2cf9870000 ptrace(PTRACE_ATTACH, 2745, 0, 0) = -1 EPERM (Operation not permitted) dup(2) = 3 fcntl(3, F_GETFL) = 0x8002 (flags O_RDWR|O_LARGEFILE) fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 9), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2cf986f000 lseek(3, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) write(3, "ptrace: Operation not permitted\n", 32ptrace: Operation not permitted ) = 32 close(3) = 0 munmap(0x7f2cf986f000, 4096) = 0 brk(0x15d3000) = 0x15d3000 write(1, "I know you're observing me, i qu"..., 35I know you're observing me, i quit ) = 35 exit_group(2) = ? +++ exited with 2 +++
Plusieurs points sont à noter :
La détection semble se faire via ptrace(), en essayant de s'attacher à son fils.
Nous allons maintenant pouvoir chercher où sont exécutés les appels système clone() et ptrace() dans notre binaire, en recherchant les instructions “syscall”, avec les numéros de syscall correspondants dans eax.
clone : LOAD:00000000004788BE mov eax, 38h LOAD:00000000004788C3 syscall ; clone LOAD:000000000040134E call clone LOAD:0000000000401353 mov [rbp-4Ch], eax LOAD:0000000000401356 cmp dword ptr [rbp-4Ch], 0 LOAD:000000000040135A jnz loc_416777 ptrace : LOAD:0000000000479EE3 mov eax, 65h LOAD:0000000000479EE8 syscall LOAD:0000000000416803 call ptrace
On constate alors le comportement suivant :
On constate également que la chaine “I know you're observing me, i quit” peut être affichée 3 fois :
Les deux dernières conditions permettent de vérifier qu'aucun breakpoint n'a été posé dans le père ou le fils, la fonction en 0x416CE8 se chargeant de calculer un CRC32.
Une fois ce comportement identifié, nous pouvons suivre les actions effectuées depuis le premier ptrace en 0x416803 :
LOAD:0000000000416A16 call waitpid LOAD:0000000000416A1B mov eax, [rbp-72Ch] LOAD:0000000000416A21 mov [rbp-710h], eax LOAD:0000000000416A27 mov eax, [rbp-710h] LOAD:0000000000416A2D movzx eax, al LOAD:0000000000416A30 cmp eax, 7Fh LOAD:0000000000416A33 jnz loc_416ACA LOAD:0000000000416A39 mov eax, [rbp-72Ch] LOAD:0000000000416A3F mov [rbp-700h], eax LOAD:0000000000416A45 mov eax, [rbp-700h] LOAD:0000000000416A4B and eax, 0FF00h LOAD:0000000000416A50 sar eax, 8 LOAD:0000000000416A53 cmp eax, 11 LOAD:0000000000416A56 jnz short loc_416AAA
LOAD:0000000000416A69 mov ecx, 0 LOAD:0000000000416A6E mov edx, 80h LOAD:0000000000416A73 mov esi, eax LOAD:0000000000416A75 mov edi, PTRACE_PEEKUSER LOAD:0000000000416A7A mov eax, 0 LOAD:0000000000416A7F call ptrace
0: e8 88 05 00 00 call 0x58d 5: 50 push eax 6: 60 pusha 7: 22 .byte 0x22
Une stratégie de résolution est alors envisageable :
La vérification de CRC peut être bypassée en utilisant des breakpoints hardware.
$ gdb ./crackme-05 gdb$ r Starting program: /home/fab/challenge/hackingweek2014/crackme-05 Enter password please : ^C Program received signal SIGINT, Interrupt. gdb$ hb *0x416a84 Hardware assisted breakpoint 1 at 0x416a84 gdb$ c Continuing. test_password -----------------------------------------------------------------------------------------------------------------------[regs] RAX: 0x000000000041672B RBX: 0x00007FFFFFFFD2E0 RBP: 0x00007FFFFFFFDB20 RSP: 0x00007FFFFFFFD2E0 o d I t s Z a P c RDI: 0x0000000000000003 RSI: 0x0000000000000B8F RDX: 0x0000000000000080 RCX: 0x0000000000479EEA RIP: 0x0000000000416A84 R8 : 0x0000000000000002 R9 : 0x0000000000000000 R10: 0x00007FFFFFFFD288 R11: 0x0000000000000246 R12: 0x0000000026D87AC4 R13: 0x0000000000000000 R14: 0x0000000000025F78 R15: 0x0000000000000000 CS: 0033 DS: 0000 ES: 0000 FS: 0063 GS: 0000 SS: 002B -----------------------------------------------------------------------------------------------------------------------[code] => 0x416a84: mov QWORD PTR [rbp-0x790],rax 0x416a8b: mov rcx,QWORD PTR [rbp-0x80] 0x416a8f: mov rdx,QWORD PTR [rbp-0x790] 0x416a96: mov eax,DWORD PTR [rbp-0x4c] 0x416a99: mov esi,eax 0x416a9b: mov edi,0x4 0x416aa0: mov eax,0x0 0x416aa5: call 0x479ea0 ----------------------------------------------------------------------------------------------------------------------------- Breakpoint 1, 0x0000000000416a84 in ?? () gdb$
La valeur de retour de ptrace(PTRACE_PEEKUSER) est donc 0x41672B.
On peut alors cette fois s'attacher au fils, et breaker juste avant, en 0x416726, puis modifier le code à l'adresse 0x41672B.
$ ./crackme-05 Enter password please : ^Z [1]+ Stoppé ./crackme-05 $ pgrep crackme-05 2962 2963 $ gdb gdb$ attach 2963 Attaching to process 2963 Reading symbols from /home/fab/challenge/hackingweek2014/crackme-05...(no debugging symbols found)...done. -----------------------------------------------------------------------------------------------------------------------[regs] RAX: 0x0000000000000000 RBX: 0x0000000000400338 RBP: 0x00007FFF7192C260 RSP: 0x00007FFF7192BA18 o d I t s z a P c RDI: 0x0000000000000B93 RSI: 0x0000000000000B93 RDX: 0x0000000000000013 RCX: 0xFFFFFFFFFFFFFFFF RIP: 0x000000000044D6F9 R8 : 0x0000000000000000 R9 : 0x0000000000000000 R10: 0x00000000010C2B50 R11: 0x0000000000000206 R12: 0x0000000000000000 R13: 0x0000000000448B40 R14: 0x0000000000448BD0 R15: 0x0000000000000000 CS: 0033 DS: 0000 ES: 0000 FS: 0063 GS: 0000 SS: 002B -----------------------------------------------------------------------------------------------------------------------[code] => 0x44d6f9: cmp rax,0xfffffffffffff000 0x44d6ff: ja 0x44d71a 0x44d701: repz ret 0x44d703: nop DWORD PTR [rax+rax*1+0x0] 0x44d708: test eax,eax 0x44d70a: jg 0x44d6e9 0x44d70c: mov ecx,eax 0x44d70e: neg ecx ----------------------------------------------------------------------------------------------------------------------------- 0x000000000044d6f9 in ?? () gdb$ hb *0x416726 Hardware assisted breakpoint 1 at 0x416726 gdb$ c ... Continuing. -----------------------------------------------------------------------------------------------------------------------[regs] RAX: 0x00007FFF7192BB38 RBX: 0x0000000000400338 RBP: 0x00007FFF7192C260 RSP: 0x00007FFF7192BA20 o d I t s Z a P c RDI: 0x0000000000000001 RSI: 0x0000000000000125 RDX: 0x0000000000000124 RCX: 0x000000003FF00000 RIP: 0x0000000000416726 R8 : 0x00000000000005B1 R9 : 0x00000000000005B2 R10: 0x00000000000005B3 R11: 0x0000000000000206 R12: 0x0000000000000000 R13: 0x0000000000448B40 R14: 0x0000000000448BD0 R15: 0x0000000000000000 CS: 0033 DS: 0000 ES: 0000 FS: 0063 GS: 0000 SS: 002B -----------------------------------------------------------------------------------------------------------------------[code] => 0x416726: mov edi,0x47 0x41672b: mov BYTE PTR ds:0x0,0x0 0x416733: xor BYTE PTR [rdx-0x41],bh 0x416736: icebp 0x416737: cld 0x416738: psraw mm1,mm6 0x41673b: add edx,0xffffffee 0x41673e: ja 0x41673c ----------------------------------------------------------------------------------------------------------------------------- Breakpoint 1, 0x0000000000416726 in ?? () gdb$ set *(0x41672b)=0x000588E8 gdb$ set *(0x41672b+4)=0x22605000 gdb$ x/8i $rip => 0x416726: mov edi,0x47 0x41672b: call 0x416cb8 0x416730: push rax 0x416731: (bad) 0x416732: and dh,BYTE PTR [rax] 0x416734: jp 0x4166f5 0x416736: icebp 0x416737: cld gdb$
L'étude du code de la fonction en 0x416cb8 nous indique qu'il ne s'agit que d'un simple XOR entre une clé fixe située en 0x4CE980 et les données situées à l'adresse de retour (soit 0x416730). Breakons en 0x416730 :
gdb$ hb *0x416730 Hardware assisted breakpoint 2 at 0x416730 gdb$ c Continuing. -----------------------------------------------------------------------------------------------------------------------[regs] RAX: 0x0000000000000000 RBX: 0x0000000000400338 RBP: 0x00007FFF7192C260 RSP: 0x00007FFF7192BA20 o d I t s Z a P c RDI: 0x00007FFF7192B927 RSI: 0x0000000000000000 RDX: 0x0000000000416770 RCX: 0x0000000000000000 RIP: 0x0000000000416730 R8 : 0x00000000000005B1 R9 : 0x00000000000005B2 R10: 0x00000000000005B3 R11: 0x0000000000000206 R12: 0x0000000000000000 R13: 0x0000000000448B40 R14: 0x0000000000448BD0 R15: 0x0000000000000000 CS: 0033 DS: 0000 ES: 0000 FS: 0063 GS: 0000 SS: 002B -----------------------------------------------------------------------------------------------------------------------[code] => 0x416730: mov rax,QWORD PTR [rbp-0x48] 0x416734: mov QWORD PTR [rbp-0x6c8],rax 0x41673b: movabs rax,0x4b603c21ce4423f0 0x416745: mov QWORD PTR [rbp-0x6d0],rax 0x41674c: mov rax,QWORD PTR [rbp-0x6c8] 0x416753: mov rdx,QWORD PTR [rax] 0x416756: movabs rax,0x26154e11af261792 0x416760: xor rax,rdx ----------------------------------------------------------------------------------------------------------------------------- Breakpoint 2, 0x0000000000416730 in ?? () gdb$ x/9i $rip => 0x416730: mov rax,QWORD PTR [rbp-0x48] 0x416734: mov QWORD PTR [rbp-0x6c8],rax 0x41673b: movabs rax,0x4b603c21ce4423f0 0x416745: mov QWORD PTR [rbp-0x6d0],rax 0x41674c: mov rax,QWORD PTR [rbp-0x6c8] 0x416753: mov rdx,QWORD PTR [rax] 0x416756: movabs rax,0x26154e11af261792 0x416760: xor rax,rdx 0x416763: cmp QWORD PTR [rbp-0x6d0],rax
On constate alors que notre mot de passe (pointeur en rbp-0x48) est xoré avec la valeur 0x26154e11af261792, puis comparée à la valeur 0x4b603c21ce4423f0. Le password attendu est donc simplement le XOR entre 0x26154e11af261792 et 0x4b603c21ce4423f0.
>>> hex(0x26154e11af261792^0x4b603c21ce4423f0) '0x6d75723061623462' >>> "6d75723061623462".decode('hex')[::-1] 'b4ba0rum'
$ ./crackme-05 Enter password please : b4ba0rum Youpi !
Enfin ! :)