31 KiB
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
- 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
- Le buffer fait 32 octets, comme révélé dans l'instruction
- 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, maismain()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.
- 33 octets : buffer rempli, le 33ème octet déborde sur le RBP.
- 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 parstrncpy()en ajoutant l'octet nul final à la main (\x00) voiresnprintf()gets()se remplace parfgets()qui requiert explicitement une taille maximale à ne pas dépasser
- 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 fonctionret
- Le Stack Canary est une protection de pile :
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
- Quels sont les 3 IoCs les plus évidents trouvés avec strings ?
- http://evil-c2.example.com/beacon
- HKLM\Software\Microsoft\Windows\CurrentVersion\Run
- /tmp/.hidden_payload
- 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
- 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.
- 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"
- 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
$ 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
- 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 unprint ptret l'adresse n'avait pas changé. Le pointeur n'a PAS été effacé. En faisant unx/10x ptron 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.
- Ligne 11, après le
- 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 unprint ptrle segment aurait été vide.
- On force le pointeur à NULL après
- Valgrind signale-t-il une erreur sur la lecture ou l'écriture, ou les deux ?
Invalid read of size 1=> lors dustrlenjuste après lefreeInvalid write of size 8=> lors dustrcpy- Donc les deux
- 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
- 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.
- 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
- Première ligne, en en-tête. Je vois que ça correspond à un offset standard, de 0x3c à 0x40, correspond à
- 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)
- En utilisant un empaqueteur anonyme ou maison... j'ai dû me renseigner sur les comportements structurels :
- 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