Files
2026-06-12 20:25:28 +02:00

31 KiB

Journée en autonomie - Semaine 10, Jour 4

vuln_stack

$ objdump -f vuln_stack

vuln_stack:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000000401050

$ checksec --file=vuln_stack
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       FortifiableFILE
Partial RELRO   No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   36 Symbols        No    0               2         vuln_stack

$ objdump -d vuln_stack | grep "greet" -A 20
0000000000401136 <greet>:
  401136:       55                      push   %rbp
  401137:       48 89 e5                mov    %rsp,%rbp
  40113a:       48 83 ec 30             sub    $0x30,%rsp

GDB

$ gdb -q ./vuln_stack
Reading symbols from ./vuln_stack...
(No debugging symbols found in ./vuln_stack)
(gdb) info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000  _init
0x0000000000401030  strcpy@plt
0x0000000000401040  printf@plt
0x0000000000401050  _start
0x0000000000401080  _dl_relocate_static_pie
0x0000000000401090  deregister_tm_clones
0x00000000004010c0  register_tm_clones
0x0000000000401100  __do_global_dtors_aux
0x0000000000401130  frame_dummy
0x0000000000401136  greet
0x0000000000401173  main
0x00000000004011c8  _fini
(gdb) break greet
Breakpoint 1 at 0x40113e
(gdb) run Albathar
Starting program: /home/kali/Jour_04/vuln_stack Albathar
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x000000000040113e in greet ()
(gdb) x/20gx $rsp
0x7fffffffdb90: 0x0000007100000017      0x0000000000000000  # Début du buffer
0x7fffffffdba0: 0x0000000000000000      0x0000000000000000
0x7fffffffdbb0: 0x0000000000000000      0x0000000000000000
0x7fffffffdbc0: 0x00007fffffffdbe0      0x00000000004011c0  # RBP sauvegardé + Adresse de retour dans main()
0x7fffffffdbd0: 0x00007fffffffdcf8      0x00000002f7fe5990
0x7fffffffdbe0: 0x00007fffffffdcf8      0x00007ffff7ddef75
0x7fffffffdbf0: 0x00007ffff7fc7000      0x0000000000401173
0x7fffffffdc00: 0x00000002ffffdce0      0x00007fffffffdcf8
0x7fffffffdc10: 0x0000000000000000      0x4e77cf51d0d6cdfe
0x7fffffffdc20: 0x0000000000000002      0x00007ffff7ffd000
(gdb) run AAAAAAAAAAAAAAAAAA
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kali/Jour_04/vuln_stack AAAAAAAAAAAAAAAAAA
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x000000000040113e in greet ()
(gdb) x/20gx $rsp
0x7fffffffdb80: 0x0000007100000017      0x0000000000000000
0x7fffffffdb90: 0x0000000000000000      0x0000000000000000
0x7fffffffdba0: 0x0000000000000000      0x0000000000000000
0x7fffffffdbb0: 0x00007fffffffdbd0      0x00000000004011c0
0x7fffffffdbc0: 0x00007fffffffdce8      0x00000002f7fe5990
0x7fffffffdbd0: 0x00007fffffffdce8      0x00007ffff7ddef75
0x7fffffffdbe0: 0x00007ffff7fc7000      0x0000000000401173
0x7fffffffdbf0: 0x00000002ffffdcd0      0x00007fffffffdce8
0x7fffffffdc00: 0x0000000000000000      0x43436e0ef6001cad
0x7fffffffdc10: 0x0000000000000002      0x00007ffff7ffd000

(gdb) run $(python3 -c "print('A'*56 + 'B'*8)")
Starting program: /home/kali/Jour_04/vuln_stack $(python3 -c "print('A'*56 + 'B'*8)")
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB!

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401172 in greet ()
(gdb) break greet
Breakpoint 1 at 0x40113e
(gdb) run $(python3 -c "print('A'*56 + 'B'*8)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kali/Jour_04/vuln_stack $(python3 -c "print('A'*56 + 'B'*8)")
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x000000000040113e in greet ()
(gdb) disas greet
Dump of assembler code for function greet:
   0x0000000000401136 <+0>:     push   %rbp
   0x0000000000401137 <+1>:     mov    %rsp,%rbp
   0x000000000040113a <+4>:     sub    $0x30,%rsp
=> 0x000000000040113e <+8>:     mov    %rdi,-0x28(%rbp)
   0x0000000000401142 <+12>:    mov    -0x28(%rbp),%rdx
   0x0000000000401146 <+16>:    lea    -0x20(%rbp),%rax
   0x000000000040114a <+20>:    mov    %rdx,%rsi
   0x000000000040114d <+23>:    mov    %rax,%rdi
   0x0000000000401150 <+26>:    call   0x401030 <strcpy@plt>   # Ce qu'on cherche
   0x0000000000401155 <+31>:    lea    -0x20(%rbp),%rax        # 32 octets de buffer
   0x0000000000401159 <+35>:    mov    %rax,%rsi
   0x000000000040115c <+38>:    lea    0xea1(%rip),%rax        # 0x402004
   0x0000000000401163 <+45>:    mov    %rax,%rdi
   0x0000000000401166 <+48>:    mov    $0x0,%eax
   0x000000000040116b <+53>:    call   0x401040 <printf@plt>
   0x0000000000401170 <+58>:    nop
   0x0000000000401171 <+59>:    leave
   0x0000000000401172 <+60>:    ret
End of assembler dump.

(gdb) break *0x401155
Breakpoint 2 at 0x401155
(gdb) run $(python3 -c "print('A'*40 + 'B'*8)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kali/Jour_04/vuln_stack $(python3 -c "print('A'*40 + 'B'*8)")
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x000000000040113e in greet ()
(gdb) x/20gx $rsp
0x7fffffffdb60: 0x0000007100000017      0x0000000000000000
0x7fffffffdb70: 0x0000000000000000      0x0000000000000000
0x7fffffffdb80: 0x0000000000000000      0x0000000000000000
0x7fffffffdb90: 0x00007fffffffdbb0      0x00000000004011c0
0x7fffffffdba0: 0x00007fffffffdcc8      0x00000002f7fe5990
0x7fffffffdbb0: 0x00007fffffffdcc8      0x00007ffff7ddef75
0x7fffffffdbc0: 0x00007ffff7fc7000      0x0000000000401173
0x7fffffffdbd0: 0x00000002ffffdcb0      0x00007fffffffdcc8
0x7fffffffdbe0: 0x0000000000000000      0x382f5965217fbfe6
0x7fffffffdbf0: 0x0000000000000002      0x00007ffff7ffd000
(gdb) continue
Continuing.

Breakpoint 2, 0x0000000000401155 in greet ()
(gdb) x/20gx $rsp
0x7fffffffdb60: 0x0000007100000017      0x00007fffffffdfcb
0x7fffffffdb70: 0x4141414141414141      0x4141414141414141
0x7fffffffdb80: 0x4141414141414141      0x4141414141414141
0x7fffffffdb90: 0x4141414141414141      0x4242424242424242
0x7fffffffdba0: 0x00007fffffffdc00      0x00000002f7fe5990
0x7fffffffdbb0: 0x00007fffffffdcc8      0x00007ffff7ddef75
0x7fffffffdbc0: 0x00007ffff7fc7000      0x0000000000401173
0x7fffffffdbd0: 0x00000002ffffdcb0      0x00007fffffffdcc8
0x7fffffffdbe0: 0x0000000000000000      0x382f5965217fbfe6
0x7fffffffdbf0: 0x0000000000000002      0x00007ffff7ffd000
(gdb) continue
Continuing.
Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB!

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401172 in greet ()

Questions

  1. Quelle est la taille exacte du buffer déclaré ? Combien d'octets faut-il pour atteindre l'adresse de retour ?
    • Le buffer fait 32 octets, comme révélé dans l'instruction sub $0x20, %rsp
    • Il faut 40 octets pour saturer l'espace => le buffer (32) et 8 octets (RBP sauvegardé). Le retour apparaît au 41ème octet
  2. Que se passe-t-il si on passe exactement 33 octets ? 40 ? 100 ?
    • 33 octets : buffer rempli, le 33ème octet déborde sur le RBP. greet() s'exécute, mais main() aura une pile corrompue, pouvant occasionner un crash
    • 40 octets : buffer et pile remplis. strcpy() ajoute un octet nul à la fin, qui va écraser le premier octet de l'adresse retour, qui sera invalide et renverra une erreur SIGSEGV.
    • 100 octets : on écrase TOUT : buffer, pile, adresse de retour, variables, structure de main(). Le crash est immédiat et inéluctable.
  3. Pourquoi gets() et strcpy() sont-ils considérés comme dangereux ? Quelle fonction utiliser à la place ?
    • Fonctions historiques sans paramètre de sécurité passive, qui copient des données sans vérification ni gestion. D'où débordement par éclatement du buffer.
    • strcpy() se remplace par strncpy() en ajoutant l'octet nul final à la main (\x00) voire snprintf()
    • gets() se remplace par fgets() qui requiert explicitement une taille maximale à ne pas dépasser
  4. Comment le canary aurait-il détecté ce débordement ?

    Je comprends que "Canary" prend son nom des canaries de mineurs, qui "meurent" avant de se prendre un coup de grisou

    • Le Stack Canary est une protection de pile :
      • Au début : Valeur aléatoire secrète générée et placée sur la pile, entre le RBP et les variables
      • Pendant : Ecraser l'adresse de retour demanderait d'écraser le Canary
      • A la fin : Le programme compare le Canary actuel et la valeur retrouvée après l'injection. En cas de différence, il stoppe tout et renvoie une erreur *** stack smashing detected *** avant exécution de la fonction ret

sample_malware

$ objdump -f sample_malware

sample_malware:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x00000000000010a0

$ objdump -d sample_malware | grep ">:"
0000000000001000 <_init>:
0000000000001020 <puts@plt-0x10>:
0000000000001030 <puts@plt>:
0000000000001040 <fclose@plt>:
0000000000001050 <printf@plt>:
0000000000001060 <fopen@plt>:
0000000000001070 <fwrite@plt>:
0000000000001080 <sleep@plt>:
0000000000001090 <__cxa_finalize@plt>:
00000000000010a0 <_start>:
00000000000010d0 <deregister_tm_clones>:
0000000000001100 <register_tm_clones>:
0000000000001140 <__do_global_dtors_aux>:
0000000000001180 <frame_dummy>:
0000000000001189 <fake_network>:
00000000000011ae <fake_persistence>:
0000000000001236 <fake_evasion>:
0000000000001256 <main>:
00000000000012a0 <_fini>:

$ checksec --file=sample_malware
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       FortifiableFILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   47 Symbols        No    0               1         sample_malware

Vérifications de communications

$ strace ./sample_malware
execve("./sample_malware", ["./sample_malware"], 0x7fff7147a250 /* 36 vars */) = 0
brk(NULL)                               = 0x5a332bfb8000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x782c9df94000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=72343, ...}) = 0
mmap(NULL, 72343, PROT_READ, MAP_PRIVATE, 3, 0) = 0x782c9df82000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\241\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 840, 64) = 840
fstat(3, {st_mode=S_IFREG|0755, st_size=2014472, ...}) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 840, 64) = 840
mmap(NULL, 2055760, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x782c9dd8c000
mmap(0x782c9ddb4000, 1474560, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x782c9ddb4000
mmap(0x782c9df1c000, 339968, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x190000) = 0x782c9df1c000
mmap(0x782c9df6f000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e3000) = 0x782c9df6f000
mmap(0x782c9df75000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x782c9df75000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x782c9dd89000
arch_prctl(ARCH_SET_FS, 0x782c9dd89740) = 0
set_tid_address(0x782c9dd89a10)         = 1444
set_robust_list(0x782c9dd89a20, 24)     = 0
rseq(0x782c9dd89680, 0x20, 0, 0x53053053) = 0
mprotect(0x782c9df6f000, 16384, PROT_READ) = 0
mprotect(0x5a33218fd000, 4096, PROT_READ) = 0
mprotect(0x782c9dfd2000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
getrandom("\xd4\x25\xe5\x11\xe9\xd4\x3b\xa3", 8, GRND_NONBLOCK) = 8
munmap(0x782c9df82000, 72343)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
brk(NULL)                               = 0x5a332bfb8000
brk(0x5a332bfd9000)                     = 0x5a332bfd9000
write(1, "[malware_demo] Demarrage simulat"..., 36[malware_demo] Demarrage simulation
) = 36
write(1, "[*] Verification debugger (IsDeb"..., 56[*] Verification debugger (IsDebuggerPresent simule)...
) = 56
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffcc966cf10) = 0
write(1, "[*] Tentative de connexion vers "..., 66[*] Tentative de connexion vers http://evil-c2.example.com/beacon
) = 66
write(1, "[*] Tentative d'ecriture : /tmp/"..., 48[*] Tentative d'ecriture : /tmp/.hidden_payload
) = 48
openat(AT_FDCWD, "/tmp/.hidden_payload", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
write(3, "ceci est un payload pedagogique "..., 38) = 38
close(3)                                = 0
write(1, "[*] Fichier cree.\n", 18[*] Fichier cree.
)     = 18
write(1, "[malware_demo] Fin simulation\n", 30[malware_demo] Fin simulation
) = 30
exit_group(0)                           = ?
+++ exited with 0 +++
$ ltrace ./sample_malware
puts("[malware_demo] Demarrage simulat"...[malware_demo] Demarrage simulation
)                                          = 36
puts("[*] Verification debugger (IsDeb"...[*] Verification debugger (IsDebuggerPresent simule)...
)                                          = 56
sleep(1)                                                                             = 0
printf("[*] Tentative de connexion vers "...[*] Tentative de connexion vers http://evil-c2.example.com/beacon
)                                        = 66
printf("[*] Tentative d'ecriture : %s\n", "/tmp/.hidden_payload"[*] Tentative d'ecriture : /tmp/.hidden_payload
)                    = 48
fopen("/tmp/.hidden_payload", "w")                                                   = 0x567787cc1720
fwrite("ceci est un payload pedagogique "..., 1, 38, 0x567787cc1720)                 = 38
fclose(0x567787cc1720)                                                               = 0
puts("[*] Fichier cree."[*] Fichier cree.
)                                                            = 18
puts("[malware_demo] Fin simulation"[malware_demo] Fin simulation
)                                                = 30
+++ exited (status 0) +++

$ cat /tmp/.hidden_payload
ceci est un payload pedagogique benin

GDB

$ gdb -q ./sample_malware
Reading symbols from ./sample_malware...
(No debugging symbols found in ./sample_malware)
(gdb) info functions
All defined functions:

Non-debugging symbols:
0x0000000000001000  _init
0x0000000000001030  puts@plt
0x0000000000001040  fclose@plt
0x0000000000001050  printf@plt
0x0000000000001060  fopen@plt
0x0000000000001070  fwrite@plt
0x0000000000001080  sleep@plt
0x0000000000001090  __cxa_finalize@plt
0x00000000000010a0  _start
0x00000000000010d0  deregister_tm_clones
0x0000000000001100  register_tm_clones
0x0000000000001140  __do_global_dtors_aux
0x0000000000001180  frame_dummy
0x0000000000001189  fake_network
0x00000000000011ae  fake_persistence
0x0000000000001236  fake_evasion
0x0000000000001256  main
0x00000000000012a0  _fini

Questions

  1. Quels sont les 3 IoCs les plus évidents trouvés avec strings ?
  2. strace montre-t-il un appel connect() réseau ? Pourquoi ?
    • On voit de multiples "nmap" effectués. Les prints vers le faux site ne comptent pas. Il cherche certainement à sonder le réseau local pour s'étendre
  3. Le fichier /tmp/.hidden_payload est-il visible avec ls /tmp/ ? Avec ls -la /tmp/ ?
    • Non, le . de départ le met comme fichier caché. LS par défaut ne l'affichera pas
    • Oui, avec le -a. marche aussi avec le -A.
  4. Comment un vrai malware rendrait-il ces strings moins détectables ? (XOR, base64, chiffrement)
    • Un encodage en base64 se décoderait assez vite. le mieux est d'utiliser un chiffrement avec une clef obfusquée, soit qui récupère une variable sur le PC dans le poule entropique (mais qu'on peut retracer quand on a la nanoseconde d'exécution locale) soit pré-enregistrée et chiffrée dans le code, mais son mode statique rend la clef crackable "dans l'absolu"
  5. Quelle différence entre analyse statique et analyse dynamique pour ce binaire ?
    • La statique va analyser les fonctions, les adresses, les pointeurs etc
    • Le dynamique va le faire tourner et vérifier les actions de sortie

uaf_demo

text

$ objdump -f uaf_demo

uaf_demo:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000001070

$ objdump -d uaf_demo | grep ">:"
0000000000001000 <_init>:
0000000000001020 <free@plt-0x10>:
0000000000001030 <free@plt>:
0000000000001040 <printf@plt>:
0000000000001050 <malloc@plt>:
0000000000001060 <__cxa_finalize@plt>:
0000000000001070 <_start>:
00000000000010a0 <deregister_tm_clones>:
00000000000010d0 <register_tm_clones>:
0000000000001110 <__do_global_dtors_aux>:
0000000000001150 <frame_dummy>:
0000000000001159 <main>:
0000000000001218 <_fini>:

$ objdump -d uaf_demo | grep "<free" -A 10
0000000000001020 <free@plt-0x10>:
    1020:       ff 35 ca 2f 00 00       push   0x2fca(%rip)        # 3ff0 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:       ff 25 cc 2f 00 00       jmp    *0x2fcc(%rip)        # 3ff8 <_GLOBAL_OFFSET_TABLE_+0x10>
    102c:       0f 1f 40 00             nopl   0x0(%rax)

0000000000001030 <free@plt>:
    1030:       ff 25 ca 2f 00 00       jmp    *0x2fca(%rip)        # 4000 <free@GLIBC_2.2.5>
    1036:       68 00 00 00 00          push   $0x0
    103b:       e9 e0 ff ff ff          jmp    1020 <_init+0x20>

0000000000001040 <printf@plt>:
    1040:       ff 25 c2 2f 00 00       jmp    *0x2fc2(%rip)        # 4008 <printf@GLIBC_2.2.5>
    1046:       68 01 00 00 00          push   $0x1
    104b:       e9 d0 ff ff ff          jmp    1020 <_init+0x20>

valgrind

$ ./uaf_demo
Avant free : donnees sensibles
Apres free : _p,
Apres reutilisation : ecrasement UAF

$ ./uaf_demo
Avant free : donnees sensibles
Apres free : /
Apres reutilisation : ecrasement UAF

$ valgrind --leak-check=full ./uaf_demo
==1869== Memcheck, a memory error detector
==1869== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==1869== Using Valgrind-3.25.1 and LibVEX; rerun with -h for copyright info
==1869== Command: ./uaf_demo
==1869==
Avant free : donnees sensibles
==1869== Invalid read of size 1
==1869==    at 0x4852CE6: strlen (vg_replace_strmem.c:506)
==1869==    by 0x48D7ACF: __printf_buffer (vfprintf-process-arg.c:443)
==1869==    by 0x48D87A0: __vfprintf_internal (vfprintf-internal.c:1543)
==1869==    by 0x48CD26A: printf (printf.c:33)
==1869==    by 0x40011D5: main (uaf_demo.c:11)
==1869==  Address 0x4a6b040 is 0 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
==1869== Invalid read of size 1
==1869==    at 0x4852CF4: strlen (vg_replace_strmem.c:506)
==1869==    by 0x48D7ACF: __printf_buffer (vfprintf-process-arg.c:443)
==1869==    by 0x48D87A0: __vfprintf_internal (vfprintf-internal.c:1543)
==1869==    by 0x48CD26A: printf (printf.c:33)
==1869==    by 0x40011D5: main (uaf_demo.c:11)
==1869==  Address 0x4a6b041 is 1 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
==1869== Invalid read of size 1
==1869==    at 0x4857730: memmove (vg_replace_strmem.c:1415)
==1869==    by 0x48CDBF7: memcpy (string_fortified.h:29)
==1869==    by 0x48CDBF7: __printf_buffer_write (Xprintf_buffer_write.c:39)
==1869==    by 0x48D63B1: __printf_buffer (vfprintf-process-arg.c:471)
==1869==    by 0x48D87A0: __vfprintf_internal (vfprintf-internal.c:1543)
==1869==    by 0x48CD26A: printf (printf.c:33)
==1869==    by 0x40011D5: main (uaf_demo.c:11)
==1869==  Address 0x4a6b040 is 0 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
==1869== Invalid read of size 1
==1869==    at 0x485773D: memmove (vg_replace_strmem.c:1415)
==1869==    by 0x48CDBF7: memcpy (string_fortified.h:29)
==1869==    by 0x48CDBF7: __printf_buffer_write (Xprintf_buffer_write.c:39)
==1869==    by 0x48D63B1: __printf_buffer (vfprintf-process-arg.c:471)
==1869==    by 0x48D87A0: __vfprintf_internal (vfprintf-internal.c:1543)
==1869==    by 0x48CD26A: printf (printf.c:33)
==1869==    by 0x40011D5: main (uaf_demo.c:11)
==1869==  Address 0x4a6b042 is 2 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
Apres free : donnees sensibles
==1869== Invalid write of size 8
==1869==    at 0x40011E4: main (uaf_demo.c:12)
==1869==  Address 0x4a6b040 is 0 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
==1869== Invalid write of size 8
==1869==    at 0x40011F1: main (uaf_demo.c:12)
==1869==  Address 0x4a6b047 is 7 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
==1869== Invalid read of size 2
==1869==    at 0x4857700: memmove (vg_replace_strmem.c:1415)
==1869==    by 0x48CDBF7: memcpy (string_fortified.h:29)
==1869==    by 0x48CDBF7: __printf_buffer_write (Xprintf_buffer_write.c:39)
==1869==    by 0x48D63B1: __printf_buffer (vfprintf-process-arg.c:471)
==1869==    by 0x48D87A0: __vfprintf_internal (vfprintf-internal.c:1543)
==1869==    by 0x48CD26A: printf (printf.c:33)
==1869==    by 0x400120F: main (uaf_demo.c:13)
==1869==  Address 0x4a6b040 is 0 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
==1869== Invalid read of size 2
==1869==    at 0x485770F: memmove (vg_replace_strmem.c:1415)
==1869==    by 0x48CDBF7: memcpy (string_fortified.h:29)
==1869==    by 0x48CDBF7: __printf_buffer_write (Xprintf_buffer_write.c:39)
==1869==    by 0x48D63B1: __printf_buffer (vfprintf-process-arg.c:471)
==1869==    by 0x48D87A0: __vfprintf_internal (vfprintf-internal.c:1543)
==1869==    by 0x48CD26A: printf (printf.c:33)
==1869==    by 0x400120F: main (uaf_demo.c:13)
==1869==  Address 0x4a6b044 is 4 bytes inside a block of size 64 free'd
==1869==    at 0x484C87F: free (vg_replace_malloc.c:989)
==1869==    by 0x40011BA: main (uaf_demo.c:9)
==1869==  Block was alloc'd at
==1869==    at 0x4849818: malloc (vg_replace_malloc.c:446)
==1869==    by 0x400116A: main (uaf_demo.c:6)
==1869==
Apres reutilisation : ecrasement UAF
==1869==
==1869== HEAP SUMMARY:
==1869==     in use at exit: 0 bytes in 0 blocks
==1869==   total heap usage: 2 allocs, 2 frees, 1,088 bytes allocated
==1869==
==1869== All heap blocks were freed -- no leaks are possible
==1869==
==1869== For lists of detected and suppressed errors, rerun with: -s
==1869== ERROR SUMMARY: 59 errors from 8 contexts (suppressed: 0 from 0)

GDB

$ gdb -q ./uaf_demo
Reading symbols from ./uaf_demo...
(gdb) break main
Breakpoint 1 at 0x1161: file uaf_demo.c, line 6.
(gdb) run
Starting program: /home/kali/Jour_04/uaf_demo
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at uaf_demo.c:6
⚠️ warning: 6   uaf_demo.c: No such file or directory
(gdb) print ptr
$1 = 0x7ffff7fe5990 <dl_main> "Uf\017\357\300H\211\345AWAVAUI\211\315ATI\211\374SH\201\354x\002"
(gdb) x/1s ptr
0x7ffff7fe5990 <dl_main>:       "Uf\017\357\300H\211\345AWAVAUI\211\315ATI\211\374SH\201\354x\002"
(gdb) next
7       in uaf_demo.c
(gdb) print ptr
$2 = 0x555555559310 ""
(gdb) x/1s ptr
0x555555559310: ""
(gdb) next
8       in uaf_demo.c
(gdb) print ptr
$3 = 0x555555559310 "donnees sensibles"
(gdb) x/1s ptr
0x555555559310: "donnees sensibles"
(gdb) x/10x ptr
0x555555559310: 0x64    0x6f    0x6e    0x6e    0x65    0x65    0x73    0x20
0x555555559318: 0x73    0x65
(gdb) next
Avant free : donnees sensibles
9       in uaf_demo.c
(gdb) print ptr
$4 = 0x555555559310 "donnees sensibles"
(gdb) x/1s ptr
0x555555559310: "donnees sensibles"
(gdb) next
11      in uaf_demo.c
(gdb) print ptr
$5 = 0x555555559310 "YUUU\005"
(gdb) x/1s ptr
0x555555559310: "YUUU\005"
(gdb) x/10x ptr
0x555555559310: 0x59    0x55    0x55    0x55    0x05    0x00    0x00    0x00
0x555555559318: 0x8c    0xcb
(gdb) continue
Continuing.
Apres free : YUUU
Apres reutilisation : ecrasement UAF
[Inferior 1 (process 1979) exited normally]
(gdb) quit

Questions

  1. Pourquoi la ligne "Après free" affiche-t-elle encore les données ? Est-ce garanti ?
    • Ligne 11, après le free, j'ai fait un print ptr et l'adresse n'avait pas changé. Le pointeur n'a PAS été effacé. En faisant un x/10x ptr on a surtout vu des données devenues YUUU etc. free() ne nettoie pas la mémoire pour gagner sur les perfs, et marque la zone comme disponible. Alors la librairie C la réutilise. Mais ce n'est pas garanti, c'est indéterminé. Au p'tit bonheur la chance.
  2. Quelle bonne pratique évite ce bug ? (ptr = NULL après free)
    • On force le pointeur à NULL après free(prt). Si on avait voulu faire un print ptr le segment aurait été vide.
  3. Valgrind signale-t-il une erreur sur la lecture ou l'écriture, ou les deux ?
    • Invalid read of size 1 => lors du strlen juste après le free
    • Invalid write of size 8 => lors du strcpy
    • Donc les deux
  4. Dans quel type de programme réel ce pattern est-il le plus dangereux ?
    • Les navigateurs internet pour corrompre de la mémoire et lancer des programmes à distance
    • Les noyaux d'OS (corruption, élévation de privilèges, etc)
    • Les services sur réseau (FTP/SMB, Chiffreurs...) pour divulguer des secrets

crackme_packed

$ file crackme_packed
crackme_packed: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header

$ objdump -f crackme_packed

crackme_packed:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00000000004056d8

$ objdump -d crackme_packed

crackme_packed:     file format elf64-x86-64

$ strings crackme_packed | head -20
UPX!
'@6!O
e-!x7
,!O\
 />@
/lib64
nux-x86-
.so.2G
puts
c_start_ma
rcmp
6GLIBC_2
;34@gmon_V
i       e0
PTE1
H=(@@
Usage: %s <password>
3v3rs3_M3
Bien joue ! F
LAG 3CTF{cl34n_cr4ckm3}

hexdump -C crackme_packed | grep -b "UPX!"
1029:000000e0  10 00 00 00 00 00 00 00  b4 fd 70 eb 55 50 58 21  |..........p.UPX!|
14380:00000b70  81 fe 55 50 58 21 75 11  2f 7d 00 30 b5 26 eb 04  |..UPX!u./}.0.&..|
29232:00001730  ff 00 00 00 00 55 50 58  21 00 00 00 00 00 00 00  |.....UPX!.......|
29311:00001740  55 50 58 21 0e 16 02 08  04 f6 53 57 3d c8 02 68  |UPX!......SW=..h|
$ upx -d crackme_packed -o crackme_unpacked
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.4       Markus Oberhumer, Laszlo Molnar & John Reiser    May 9th 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     25451 <-      5988   23.53%   linux/amd64   crackme_unpacked

Unpacked 1 file.
$ file crackme_unpacked
crackme_unpacked: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=52cf967f660a06f4e0f0cd0bee921df5987f6d86, for GNU/Linux 3.2.0, not stripped

$ objdump -f crackme_unpacked

crackme_unpacked:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000000401060

$ objdump -d crackme_unpacked | grep ">:"
0000000000401000 <_init>:
0000000000401020 <puts@plt-0x10>:
0000000000401030 <puts@plt>:
0000000000401040 <printf@plt>:
0000000000401050 <strcmp@plt>:
0000000000401060 <_start>:
0000000000401090 <_dl_relocate_static_pie>:
00000000004010a0 <deregister_tm_clones>:
00000000004010d0 <register_tm_clones>:
0000000000401110 <__do_global_dtors_aux>:
0000000000401140 <frame_dummy>:
0000000000401146 <main>:
00000000004011c8 <_fini>:

Questions

  1. Que retourne strings sur le binaire packed avant unpacking ? Après ?
    • Avant : Empaqueté, le binaire chiffre/compresse le binaire, et rend les chaînes de caractères illisibles. Le fait de voir "UPX!" au début du binaire semble étrange. Et qu'il laisse passer "3CTF{cl34n_cr4chm3}" encore plus.
    • Après : "UPX!" a disparu (normal) et tout est récupérable en clair.
  2. Où se trouve la signature UPX dans le fichier (offset hexa) ?
    • Première ligne, en en-tête. Je vois que ça correspond à un offset standard, de 0x3c à 0x40, correspond à p_info. Ici c'était 0xe0
  3. Si le packer était personnalisé (sans signature connue), comment détecteriez-vous la compression ?
    • En utilisant un empaqueteur anonyme ou maison... j'ai dû me renseigner sur les comportements structurels :
      • L'analyse du niveau d'entropie, 5 ou 6 sur un binaire "normal", plutôt 8 sur un binaire compressé
      • Absences ou anomalies dans les sections
      • Un nombre anormalement faible de fonctions (il faut quand même une certaine base pour qu'un binaire fonctionne)
      • La différence de poids entre le fichier binaire et la place tenue dans le poule mémoire (donc sa "vraie" taille une fois décompressée)
  4. Quel intérêt pour un malware d'utiliser un packer ? Quelles limites ?
    • Echapper à la détection statique, au profit d'une dynamique (il faut lancer le programme pour alerter le système)
    • Ralentir l'ingénierie inversée
    • Néanmoins, les antivirus modernes sont pas stupides, ils bloquent les instructions mémoire à bas niveau, et utiliser un empaqueteur générique comme UPX ne trompera aucun antivirus ni aucun EDR énervé. Et le comportement d'exécution restera identique, les antivirus se basent là-dessus surtout