====== Reverse 5 ======
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
[pid 2744] fstat(1,
[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
[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,
[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 :
* Le binaire se clone()
* Le binaire tente de ptracer son fils
* Une détection de debug est présente, et le binaire quitte ("I know you're observing me, i quit")
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 :
* Le binaire se clone
* Le fils se stoppe, le père saute en 0x416777
* Le père tente de ptracer le fils
On constate également que la chaine "I know you're observing me, i quit" peut être affichée 3 fois :
* Si le ptrace sur le fils échoue
* Si le code du père a été altéré
* Si le code du fils a été altéré
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 :
* ptrace sur le fils
* vérification du crc32 du père
* vérification du crc32 du fils (via multiples appels à ptrace(PTRACE_PEEKDATA))
* ptrace(PTRACE_POKETEXT) pour écrire le mot de passe dans la mémoire du fils
* ptrace(PTRACE_CONT) pour que le fils continue son exécution
* waitpid() sur le fils (syscall 61) et boucle tant que son état n'est pas STOP, et que le signal n'est pas 11 (correspondant à SEGFAULT)
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
* ptrace(PTRACE_PEEKUSER) pour récupérer la valeur du registre RIP
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
* ptrace(PTRACE_POKETEXT) pour remplacer les instructions à l'adresse RIP du fils avec la valeur 0x22605000000588E8, ce qui correspond au code suivant:
0: e8 88 05 00 00 call 0x58d
5: 50 push eax
6: 60 pusha
7: 22 .byte 0x22
* ptrace(PTRACE_CONT) pour relancer l'exécution du fils
* waitpid() sur le fils pour attendre sa terminaison, et vérifier sa valeur de retour. Si c'est 0, alors le serial est bon
Une stratégie de résolution est alors envisageable :
* debug du père pour récupérer la valeur de RIP lors du crash du fils
* relance
* debug du fils pour breaker juste avant le RIP fatal, faire la modification à la main, et continuer pour trouver la routine de vérification
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 ! :)