Outils d'utilisateurs

Outils du Site


hackingweek_2014:reverse:reverse5

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 <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 :

  • 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 ! :)

hackingweek_2014/reverse/reverse5.txt · Dernière modification: 2017/04/09 15:33 (modification externe)