707 lines
31 KiB
Markdown
707 lines
31 KiB
Markdown
# Journée en autonomie - Semaine 10, Jour 4
|
|
|
|
## vuln_stack
|
|
|
|
```bash
|
|
$ 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
|
|
|
|
```bash
|
|
$ 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
|
|
|
|
```bash
|
|
$ 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
|
|
|
|
```bash
|
|
$ 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 +++
|
|
```
|
|
|
|
```bash
|
|
$ 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
|
|
|
|
```bash
|
|
$ 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 ?
|
|
- http://evil-c2.example.com/beacon
|
|
- HKLM\Software\Microsoft\Windows\CurrentVersion\Run
|
|
- /tmp/.hidden_payload
|
|
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](about:blank#blocked)
|
|
```bash
|
|
$ 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
|
|
|
|
```bash
|
|
$ ./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
|
|
|
|
```bash
|
|
$ 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
|
|
|
|
```bash
|
|
$ 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|
|
|
```
|
|
|
|
```bash
|
|
$ 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.
|
|
```
|
|
|
|
```bash
|
|
$ 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 |