BlindRead

By Christal avec un Addentum de TeeJI

BlindRead est un petit programme (moins de 400ko) qui présente l'intérêt d'être truffé d'AntiSice.
Le texte qui va suivre ne traite que de la partie propre aux anti XXX, et à la manière de les contourner.

What the matter ?

Le programme est compressé par UPX

UPX ?
Quelle version ?

Un rapide coup d'śil dans un Hexediteur, et vous aurez l'information recherchée :

Info: This file is packed with the UPX executable packer
http://upx.tsx.org
UPX 1.00 Copyright (C) 1996-2000
the UPX Team.
All Rights Reserved.

Le Symbol Loader ne breakant en général pas sur l'Entry Point d'un programme compressé, regardons du coté des sections :

UPX0     000A3000     00001000     00000000     00000400     E0000080
UPX1     0004C000     000A4000     0004C000     00000400     E0000040
.rsrc    00002000     000F0000     00001E00     0004C400     C0000040

Après modifications des caractéristiques avec un outil comme ProcInfo (by TeeJi), celles ci vont prendre la valeur E0000020, et permettre ainsi un break sur l'EntryPoint, avec un débuggeur comme TRW2000 (pour changer…) ou SoftIce :

Entry Point:

:004EFD30  60                  PUSHAD  > sauvegarde des registres
:004EFD31  BE00404A00          MOV     ESI,004A4000
:004EFD36  8DBE00D0F5FF        LEA     EDI,[ESI+FFF5D000]
:004EFD3C  C787D0A40A001D6F831EMOV     DWORD PTR [EDI+000AA4D0],1E836F1D
:004EFD46  57                  PUSH    EDI
:004EFD47  83CDFF              OR      EBP,-01
:004EFD4A  EB0E                JMP     004EFD5A

UPX est un compresseur, pas un système de protection comme ASProtect par exemple. Il est juste composé d'un loader qui va se charger de décompresser l'exécutable. La fin du loader est "lisible" dès l'Entry Point, non compressé et donc accessible dès le break sur l'Entry Point:

Le Passage du Relais est trouvable sans difficulté en recherchant les premiers " ADD [EAX],AL " précédés d'un POPAD:

:004EFE9A  61                  POPAD            > restitution des registres
:004EFE9B  E98493FAFF          JMP     00499224 > jump to OEP
:004EFEA0  B8FE4E00C8          MOV     EAX,C8004EFE
:004EFEA5  FE4E00              DEC     BYTE PTR [ESI+00]
:004EFEA8  D0B44A              INVALID
:004EFEAB  0000                ADD     [EAX],AL
:004EFEAD  0000                ADD     [EAX],AL

Un jmp EIP à la place du jmp 00499224 va permettre d'obtenir un Dump de l'exécutable d'origine, sans pour autant supprimer ce que le passage dans UPX aura rajouté et /ou modifié (loader, nom des sections, caractéristiques...)

UPX0    000A3000   00001000   000A2FD0   00000600   E0000020
UPX1    0004C000   000A4000   0004BEAC   000A3600   E0000020
.rsrc   00002000   000F0000   00001D40   000EF600   E0000020

Seuls les raw Size et Raw Offset auront changé…

Créer un dump:

Il y a de nombreuses solutions pour obtenir un Dump d'un programme tournant en mémoire:

TRW2000 for Windows 9x

D'après l'auteur, cet utilitaire est plus puissant que SoftIce.
Depuis les versions 2000( j'en étais resté aux premières versions…), son utilisation a nettement été améliorée, et il faut reconnaître qu'il s'avère parfois mieux " adapté " à notre quête :

- il est beaucoup moins recherché par les schémas de protections que SoftIce
- il pose moins de problèmes de configuration, et il est d'utilisation plus immédiate, se lançant sous Windows sans avoir à rebooter le système :
- il est équipé maintenant d'un espèce de symbol loader


- son écran est identique, à peu de chose prés, avec celui de SoftIce :

- et les commandes sont presque toutes les mêmes que son jumeau. Pas de dépaysement, et surtout une prise en main des plus rapide pour peu que vous connaissiez son " concurrent "…
- il dispose, " en série ", de commandes supplémentaires à SoftIce comme PageIn, PeDump et MakePE :

command: MAKEPE
syntax: MAKEPE filename [imte]
Make a valid PE file from program's memory image. Current EIP will be entrypoint of new PE.
For DLL, you must specify its imte. Command 'mod32 dllname' will show its imte.
Autant de commandes qu'habituellement il faut rajouter, par Plugg In, à SoftIce :

Pour réaliser un dump du Process en cours, il faut simplement entrer la commande : " pedump "

PE dump

VirtualSize RVA PhysicalSize PhysicalOffset
----------
   a3000     1000        0      400
   4c000    a4000    4c000      400
    2000    f0000     1e00    4c400

Writing DOS head
Writing PE head, from 81683218, len f8+78
Writing 4001f0 len f1e10

Et voilà ! Merci El CaRaCoL...
Il a suffit de tracer (ou de poser un bpx) sur le Popad/Jmp OEP, puis de taper la commande PeDump, et TRW2000 se charge de vous proposer sous le nom Dump1.exe une copie parfaite de votre programme décompressé.
C'est immédiat, rapide, et idéal pour des débutants...

Sous SoftIce, il fallait utiliser un Add'on (excellent d'ailleurs) :

ICEdump:

Avec la syntaxe suivante:

PAGEIN <address> [<length> <file name>]
PAGEIN 400000 (adresse de l'image base) 12000 (pour 1,2Mo) c:\dumpBR.exe

Encore faut-il être en mesure de déterminer la taille que devra faire le dump du programme décompressé...

Il est aussi possible d'utiliser :

ProcDump by G-RoM, Lorian et Stone:

Option: Rebuild Import Table
Clic gauche: sur le nom du programme dans la fenêtre des taches, et sélection de "Dump Full"
PeEditor: remplacer l'entry point du programme compressé par 99224 (et non pas 499224...)

ProcDump by G-RoM, Lorian et Stone :

Encore, mais en utilisant la fonction Unpack cette fois ci:

Vous verrez défiler les informations suivantes:

BreakPoint reached at 004EFD30 (l'entry point de BlindRead)
Setting break point at 004C4E40 (le passage du relais)

L'UnPacker de ProcDump utilise un script pour trouver la signature du packer, place un BreakPoint, un peu à la façon de SoftIce, puis trace le programme après avoir relevé l'EIP du dump qui va être fait :

[UPX]
L1=OBJR
L2=LOOK 61,E9  > la signature d'UPX
L3=BP          > pose d'un breakpoint avec sauvegarde de l'EIP
L4=STEP        > effectue le dump à partir de l'OEP

La signature trouvée correspond au POPAD, et à la première instruction du JUMP :

:004EFE9A  61           POPAD            > restitution des registres
:004EFE9B  E98493FAFF   JMP     00499224 > jump to OEP

Place aux nouveaux:

ProcDump n'étant plus développé, d'autres utilitaires du même type commencent à apparaître. L'un des plus fidèles à ce merveilleux outil qu'est (était ?) ProcdDump et un des plus performant de ceux que j'ai pu utiliser est celui de TeeJi :

ProcInfo by TeeJi


La démarche est identique à celle à utiliser pour obtenir un Dump avec Procdump :

- Repérer le passage du relais (souvent un POPAD suivi d'un JMP EAX ou d'un CALL EAX. Ici un tout simple JMP OEP))
- Remplacer le jmp vers l'Original Entry Point (OEP) par un JMP EIP (adresse courante)
- Quitter le débuggeur

Dans l'image ci dessus, vous pourrez voir les caractéristiques de la première section (UPX0) que j'ai misent à E0000020 (pour obtenir un break sur l'Entry Point de ma cible), et l'Entry Point modifié (instead of the original EFD30) pour que mon Dump puisse se lancer sans passer par le loader d'UPX.

Le Dump étant opérationnel, continuons…
Un désassemblage avec Wdasm ne va donner aucune StringData Ref, et des outils comme Exescope ou Restorator ne vont pas mieux s'en sortir : " Corrupt ressource ".
Un petit pas de plus dans la découverte du schéma de protection ?
Les ressources ont été bidouillées ?

Pour en avoir le cśur net, le plus simple va être d'utiliser directement UPX.exe
Et Oui!
Ce freeware, loin d'être un système de protection, ne joue que le rôle de packer, et prévoit dans sa syntaxe de procéder à l'opération inverse!

 Ultimate Packer for eXecutables
                Copyright (C) 1996, 1997, 1998, 1999, 2000
 UPX v1.01        Markus F.X.J. Oberhumer & Laszlo Molnar       Apr 9th 2000
 
 Usage: upx [-123456789dlthVL] [-qvfk] [-o file] file..
 
 Commands:
   -1     compress faster                   -9    compress better
   -d     decompress                        -l    list compressed file
   -t     test compressed file              -V    display version number
   -h     give more help                    -L    display software license
 Options:
   -q     be quiet                          -v    be verbose
   -oFILE write output to `FILE'
   -f     force compression of suspicious files
   -k     keep backup files
   file.. executables to (de)compress 

L'option -d va permettre de retrouver un exécutable totalement identique à l'original, avec toutes les sections, ressources et StringData possibles et imaginables:

c:\upx.exe -d c:\temp\blinread.exe

 Ultimate Packer for eXecutables
                Copyright (C) 1996, 1997, 1998, 1999, 2000
 UPX v1.01      Markus F.X.J. Oberhumer & Laszlo Molnar      Apr 9th 2000
 
         File size        Ratio      Format      Name
    -------------------   ------   -----------   -----------
     944640 ->   320000   33.88%    win32/pe     c:\temp\blindread.exe 

Unpacked 1 file.

Voici l'allure qu'on pris les sections :

CODE      00098408   00001000   00098600   00000400   60000020
DATA      00010DA4   0009A000   00010E00   00098A00   C0000040
BSS       00000BF1   000AB000   00000000   000A9800   C0000000
.idata    00002568   000AC000   00002600   000A9800   C0000040
.tls      00000010   000AF000   00000000   000ABE00   C0000000
 .rdata   00000018   000B0000   00000200   000ABE00   50000040
.reloc    0000A5AC   000B1000   0000A600   000AC000   50000040
 .rsrc    00030400   000BC000   00030400   000B6600   50000040

Lancement d'un des Dump ou de la version Unpacked, et :

" This software is not designed to operate under a debugger "


Petit coup d'śil dans les StringData's :

\\.\NTICE
\\.\SICE
\\.\SIWVID

Ben tiens…
Et voyons maintenant ce que va donner FrogsIce :

=> Blindrea
** SOFTICE DETECTION ** code 0B, at cs:00492BA1
Attempting to load: NTICE (string ref at cs:00492C3C)

=> Blindrea
** SOFTICE DETECTION ** code 0B, at cs:00492BDC
Attempting to load: SIWVID (string ref at cs:00492CA8)

=> Blindrea
** SOFTICE DETECTION ** code 0B, at cs:00492C17
Attempting to load: SICE (string ref at cs:00492CB4)

=> Blindrea
** SOFTICE DETECTION ** code 07, at FCAF: 00492E2A
Interrupt:68h >eax=00C84348h ebx=00C8A648h ecx=0072FC44h
edx=00000000h esi=00C88B30h edi=00C7C394h ebp=00C8A648h

=> Blindrea
** SOFTICE DETECTION ** code 01, at 017F:0000FFFD
Interrupt:03h >eax=00000004h ebx=00C8A648h ecx=0072FC44h
edx=00000000h esi=00C88B30h edi=00C7C394h >ebp=4243484Bh

SEH proc address at cs:????????

Un vrai festival :du meltIce, des INT 68, des INT 03,et de l'Except Handler. Au finish, la présence de l'execpt handler est probablement la cause de la fermeture sauvage du programme, tant que Sice est actif, et FrogsIce n'y peut rien…

Et bien ca va être l'occasion de mieux connaître le maniement de TRW2000…

BPX CreatFileA

1er Meltice

:00492B88  6A00                PUSH    00        > hTemplateFile
:00492B8A  6A00                PUSH    00        > file attributes
:00492B8C  6A03                PUSH    03        > dwCreationDistribution
:00492B8E  6A00                PUSH    00
:00492B90  6A02                PUSH    02        > dwShareMode
:00492B92  6800000080          PUSH    80000000  > accès (read-write) mode
:00492B97  683C2C4900          PUSH    00492C3C  > / //NTICE
:00492B9C  E84B39F7FF          CALL    KERNEL32!CreateFileA
:00492BA1  83F8FF              CMP     EAX,-01   > si <> FFFFFFFF
:00492BA4  741D                JZ      00492BC3  > bad Boy !

Un ch'tit coup d'śil sur ce qui se trouve autour de l'adresse 00492C3C va être intéressant :

\\.\NTICE...Erro
r...This softwar
e is not designe
d to operate und
er a debugger. T
he application w
ill stop....\\.\

Pour le moment, rien de particulier...
La string " The software… " est immédiatement utilisée par une MessageBox dans les lignes qui suivent le JE Bad Boy

:00492BE1  6A10             PUSH    10           > bouton de la MessageBox
:00492BE3  68482C4900       PUSH    00492C48     > titre (error...)
:00492BE8  68502C4900       PUSH    00492C50     > the software...
:00492BED  6A00             PUSH    00           > pas de fenêtre proprio
:00492BEF  E83041F7FF       CALL    USER32!MessageBoxA
:00492BF4  B801000000       MOV     EAX,00000001 > flag
:00492BF9  E8F60FF7FF       CALL    00403BF4     > vers ExitProcess

Le call 00403BF4 va être une des sorties du schéma de protection anti SoftIce et va mener vers un Exitprocess :

017F:00403BA9  833B00              CMP     DWORD PTR [EBX],00
017F:00403BAC  7508                JNZ     00403BB6
017F:00403BAE  8B06                MOV     EAX,[ESI]
017F:00403BB0  50                  PUSH    EAX
017F:00403BB1  E8EAD6FFFF          CALL    KERNEL32!ExitProcess > bad boy!

Mais aucune inversion des sauts précédents le call ExitProcess n'en viendront à bout…
La vérité est ailleurs !

Il y a 12 appels à la procédure en 00403BF4 (des call et 1 saut inconditionnel).
Nopper sauvagement ce call en question pourrait sembler suffire, mais celui qui s'est occupé de la protection a multiplié les précautions…

En utilisant un peu l'ascenseur dans la fenêtre des Data's autour du message d'erreur, on trouve d'autres informations:

SIWVID..\\.\SICE
..%........?INIC
..........?E.BRu
....KHCB.Zdg....

Tiens, tiens, tiens...
SIWVID et SICE, c'est OK, c'est pour le MeltIce :

:00492BD2  68A82C4900          PUSH    00492CA8 > \\.\SIWVID
:00492BD7  E81039F7FF          CALL    KERNEL32!CreateFileA
:00492BDC  83F8FF              CMP     EAX,-01
:00492BDF  741D                JZ      00492BFE

avec la MessageBox qui suit, et le troisième MeltIce se trouve ici :

:00492C0D 68B42C4900           push    00492CB4 > \\.\SICE
:00492C12  E8D538F7FF          CALL    KERNEL32!CreateFileA
:00492C17  83F8FF              CMP     EAX,-01
:00492C1A  741D                JZ      00492C39

Mais INIC, E.BR et KHCB vont être très intéressant à suivre...

Du coup, une recherche dans les codes de l'exécutable avec un HexEditor, histoire de fouiner un peu, va donner en prime

C:\ntice\nmtrans.dll.
NmSymIsSoftICELoaded


Le premier est trouvable dans les StringDataRef, le second dans la liste des API appellées.

Par contre, chose étonnante, un outil comme FileMon ne va détecter qu'un seul essai en lecture du fichier NMTRANS pour NTIce

Blindrea	Attributes	C:\NTICE\NMTRANS.DLL	NOTFOUND	GetAttributes

Et aucune tentative pour son équivalent Sice, qui utilise pourtant le même fichier !
Et puis pourquoi avoir choisi c:\ntice comme racine ?
C'est plutôt dans le fichier Program Files\Numega\NTICE qu'il aurait fallu regarder, tant qu'à faire...

Tant pis, passons...

KHCB, en verlan ASM

En bon François, nous aurions BCHK, ou encore BoundChecker !

Petite recherche dans le Dead Listing sur la correspondance hexadécimale de KHCB = 4243484B

:00492DDA B804000000              mov eax, 00000004
:00492DDF BD4B484342              mov ebp, 4243484B  > BCHK
:00492DE4 CC                      int 03
:00492DE5 5A                      pop edx
:00492DE6 646789160000            mov fs:[0000], edx
:00492DEC 5A                      pop edx
:00492DED B801000000              mov eax, 00000001
:00492DF2 C3                      ret

et 

:004644C2 B804000000              mov eax, 00000004
:004644C7 BD4B484342              mov ebp, 4243484B  > BCHK
:004644CC CC                      int 03
:004644CD 5A                      pop edx 
:004644CE 646789160000            mov fs:[0000], edx 
:004644D4 5A                      pop edx
:004644D5 B801000000              mov eax, 00000001
:004644DA C3                      ret

Oups !
Soit FrogsIce nous en a " oublié " un, soit le programme n'a pas eu l'occasion de passer par les DEUX Int 03 !

Et en dessous de l'int 03 vous voyez un mov fs:[0000], edx !
Mais c'est que ca ressemble furieusement à une SEH ça, comme le suggère FrogsIce dans son rapport de détection!

Voyons la suite.
Dans la fenêtre des Data's, j'avais aussi repéré :

INIC et E.BR

Les strings que softice laisse traîner en mémoire :

SICE a la fâcheuse manie de laisser traîner derrière lui un certain nombre de chaînes de caractères...
WINICE
Par exemple !

Le sachant, il est facile de faire une recherche dans la mémoire de la présence d'un " W ", puis de contrôler que celui ci est suivi de INIC (sur un Dword). On trouvera alors une recherche de ce type :

:00492D32 B057          mov al, 57          > " W "
:00492D34 BF00000100    mov edi, 00010000   > à partir de l'@ 10000
:00492D39 B900003F00    mov ecx, 003F0000   > et jusqu'à l'@ 3F0000

et dans les lignes qui suivent, tout ce qu'il faut pour initier une except handler...

:00492D3E 33D2          xor edx, edx
:00492D40 685C2D4900    push 00492D5C
:00492D45 64FF32        push dword ptr fs:[edx]
:00492D48 8925D0A84A00  mov dword ptr [004AA8D0], esp
:00492D4E 892DD4A84A00  mov dword ptr [004AA8D4], ebp
:00492D54 648922        mov dword ptr fs:[edx], esp

avant un saut

:00492D57 EB1A           jmp 00492D73

qui nous mènera droit vers un Scan de la mémoire :

:00492D73 F2             repnz
:00492D74 AE             scasb
:00492D75 E325           jcxz 00492D9C                 > branchement si CX est à 0\par
:00492D7E 813F494E4943   cmp dword ptr [edi], 43494E49 > INIC
:00492D84 7406           je 00492D8C                   > bad Boy ?
:00492D8A EBE7           jmp 00492D73                  > loop
:00492D8C 83C704         add edi, 00000004
:00492D8F 813F452E4252   cmp dword ptr [edi], 52422E45 > on compare la fin
                                                       > soit WINICE.BR->E.BR
:00492D95 75DC           jne 00492D73                  > continue la recherche
:00492D97 B801000000     mov eax, 00000001             > Flag Bad Boy
:00492D9C 5A             pop edx
:00492D9D 646789160000   mov fs:[0000], edx            > et revoilà notre Except handler !
:00492DA3 5A             pop edx
:00492DA4 C3             ret

Une nouvelle recherche dans le Dead Listing va nous redonner le même, le pareil (vive le copié/Collé, n'est ce pas ByteCop...)

:00464477 813F452E4252            cmp dword ptr [edi], 52422E45
:0046447D 75DC                    jne 0046445B
:0046445B F2                      repnz
:0046445C AE                      scasb
:0046445D E325                    jcxz 00464484
:00464466 813F494E4943            cmp dword ptr [edi], 43494E49
:0046446C 7406                    je 00464474
:00464472 EBE7                    jmp 0046445B

Bon, deux détections supplémentaires de la présence de SICE...

Et l'INT 68, faudrait voir à pas l'oublier...

Pour mettre la main sur l'INT 68, il n'y a qu'à se laisser guider par les adresses données par FrogsIce:

BPM 492E2A X

:00492E28  B443          MOV     AH,43         > voir le Code.txt de FrogsIce
:00492E2A  CD68          INT     68
:00492E2C  5A            POP     EDX
:00492E2D  646789160000  MOV     FS:[0000],EDX > encore de l'except handler...
:00492E33  5A            POP     EDX
:00492E34  663D86F3      CMP     AX,F386       > debuggeur detected ? 
:00492E38  7407          JZ      00492E41
:00492E3E  33C0          XOR     EAX,EAX       > Good Boy
:00492E40  C3            RET
:00492E41  B801000000    MOV     EAX,00000001  > Bad Boy
:00492E46  C3            RET

mais comme on ne sait jamais, une petite recherche dans le Dead Listing sur " INT 68 " va nous en donner une de plus :

:00464510 B443                    mov ah, 43
:00464512 CD68                    int 68
:00464514 5A                      pop edx
:00464515 646789160000            mov fs:[0000], edx
:0046451B 5A                      pop edx
:0046451C 663D86F3                cmp ax, F386
:00464520 7407                    je 00464529

Je ne jurerai pas que j'ai choppé tous les anti XXX, mais la petite modification suivante va suffire:

Sur le Dump décompressé obtenu :

- Remplacer les 74 (JZ) par des EB (JMP) sous les CreateFileA des trois meltIce
- Nopper les deux test INIC
- Nopper les deux INT 68
- Remplacer les INT 03 des deux BCHK par des RET

Le programme démarre, SoftIce est Oublié...
(m'en fiche, j'utilise TRW2000 qui n'est pas détecté !)

Plutôt que de patcher le dump décompressé, et se lancer dans du Hard Patching de la version compressé, un petit loader rapidement bricolé avec Risc Process Patcher va très bien faire l'affaire en attendant :

T=50000:               > temps de recherche alloué
F=blindread.exe:       > nom de la cible
O=crk_blindread.exe:   > nom du crack.exe
P=492ba4/74/eb:        > 1er meltice
P=492bdf/74/eb:        > 2ème meltice
P=492c1a/74/eb:        > 3ème meltice
P=464512/cd,68/90,90:  > 1er Int 680
P=492e2a/cd,68/90,90:  > 2ème INT 68
P=492de4/cc/C3:        > 1er INT 03
P=4644CC/cc/c3:        > 2ème INT 03
P=492d84/74,06/90,90:  > 1er INIC
P=46446C/74,06/90,90:  > 2ème INIC
$

Reboot de Windows pour réimplanter SICE (opération inutile quand vous utilisez TRW2000 !)
Test du Mémory Patcher
Glop ! Glop !

Le hardPatching de la version compressé donnerait ceci :

C605A42B4900EB      MOV     BYTE PTR [00492BA4],EB
C605DF2B4900EB      MOV     BYTE PTR [00492BDF],EB
C6051A2C4900EB      MOV     BYTE PTR [00492C1A],EB
66C705124646009090  MOV     WORD PTR [00464612],9090
66C7052A2E49009090  MOV     WORD PTR [00492E2A],9090
C605E42D4900C3      MOV     BYTE PTR [00492DE4],C3
C605CC444600C3      MOV     BYTE PTR [004644CC],C3
66C705842D49009090  MOV     WORD PTR [00492D84],9090
E94693FAFF          JMP     00499224 > et jump vers l'OEP...

Glop ! Glop !

Pour autant, si l'except handler a été shuntée (principalement) en remplaçant les INT 03 par des Ret, ca vaudrait peut être la peine de s'y intéresser de plus prêt, histoire de comprendre...

Except Handler

Quelques généralités :

Qu'est ce qu'une exception ?
C'est une offense commise par le programme envers l'OS, et qui aura comme conséquence de faire surgir une MessageBox d'erreur, voir le fameux écran bleu sans lequel Windows ne serait plus tout à fait le même…

Le principe de " l'exception handling ", souvent appelé " Structured Exception Handling " ou SEH, est que l'application installe une ou plusieurs routines appelées " exception handlers ", qui vont récupérer les erreurs commissent et les traiter en amont de la gestion des erreurs par l'OS, en lieu et place de celui ci.
Lors de la création d'une telle " exception handling ", le système va appeler une routine qui permettra à l'application de gérer l'exception.

Dans la pratique, il y a plusieurs façons de gérer des exceptions.
Parmi celle ci, il y a l'utilisation de l'API SetUnHandledExceptionFilter.
En voici un exemple, extrait du texte de TeeJi sur ZipStudio :

:004CFB14 8965FC       MOV [EBP-04],ESP 
:004CFB17 683AFB4C00   PUSH 004CFB3A      > pousse l'offset du Handler
:004CFB1C E80363F3FF   CALL KERNEL32!SetUnhandledExceptionFilter 
:004CFB21 BD4B484342   MOV EBP,4243484B   >BCHK 
:004CFB26 B804000000   MOV EAX,00000004 
:004CFB2B CC           INT 3 
:004CFB2C E8DB68F3FF   CALL USER32!MessageBoxA 
:004CFB31 E8E660F3FF   CALL KERNEL32!ExitProcess 
:004CFB36 66FA         CLI 
:004CFB38 FB           STI 
:004CFB39 A7           CMPSD 
:004CFB3A 8B65FC       MOV ESP,[EBP-04] 
:004CFB3D 6843FB4C00   PUSH 004CFB43 
:004CFB42 C3           RET 
:004CFB43 C3           RET 

Si c'est la fonction en 004CFB3A qui est exécutée, le programme ne retournera pas en 004CFB2C (après l'INT 03) mais en 004CFB43. Pour neutraliser cette protection, il faudra remplacer l'INT 03 par ADD ESP,04 ; RET.

La fonction
SetUnhandledExceptionFilter laisse une application superviser le niveau le plus haut d'une exception handler que Win32 place à la tête de chaque thread et process.

Après l'appel à cette fonction, si une exception survient dans un process qui n'est pas en cours de débuggage, ce filtre appellera l'exception filter function spécifiée par le paramètre lpTopLevelExceptionFilter.

LpTopLevelExceptionFilter :
Remplace l'adresse de la fonction " top-level exception filter " qui sera appelée même si la fonction UnhandledExceptionFilter prend le contrôle, et si le process n'est pas en cours de débuggage.

La fonction SetUnhandledExceptionFilter retourne l'adresse de la précédente exception établie avec cette fonction.

Une autre approche dans la gestion du traitement des erreurs, et d'installer un " per-thread handler " et de le faire pointer sur le premier Dword du " Thread Information Block " dont l'adresse est FS :[0000].
Voici quelques explications rapides, partiellement tirées d'un texte de Jeremy Gordon (je vous recommande aussi de regarder le très bon texte de
LuTiN NoiR sur les SEH, truffé d'exemples en asm. Good Job, Man !)

Ce type de procédé est plus particulièrement utilisé pour préserver certaines parties du code et s'établie par une modification de la valeur placée par le système à FS :[0000].
Chaque Thread dans le programme à une valeur différente pour le segment du registre FS, si bien que l'exceptionhandler sera bien spécifique.

La valeur dans FS est un sélecteur 16 bits qui pointe vers le " THREAD Information Block ", une structure qui contient d'importantes informations sur chaque Thread. Le premier DWORD de ce Block pointe vers une " ERR " structure.
Cette structure est composée de 2 DWORD comme suit :

1st dword +0

Pointe vers la structure ERR suivante

2nd dword +4

Pointe vers notre propre exception handler

Vous voyez qu'il n'est pas difficile d'installer une " per-thread exception handler " :

PUSH OFFSET HANDLER 
PUSH FS:[0]         > adresse de la structure ERR suivante
MOV FS:[0],ESP      > place l'adresse de la structure ERR dans FS :[0000]
... 
...                 > le code protégé par le handler vient ici... 
POP FS:[0]          > restore la structure ERR suivante dans FS :[0000]
ADD ESP,4h 
RET 
;*********************** 
HANDLER: 
... 
...                > l'exception handler vient ici
... 
MOV EAX,1          > EAX=1 -> continue vers l'Handler suivant
RET ;              > EAX=0 -> continue l'exécution

Dans le code ci dessus, vous pouvez voir que le 2ème Dword de la structure ERR, où se trouve l'adresse de notre handler, est placé sur la pile en premier, puis le 1er Dword de la structure ERR suivante est placé sur la pile par l'instruction PUSH FS :[0000].
Dans le cas ou le 1er handler ne correspond pas à une exception (EAX=1), alors le système appellera le handler suivant. C'est le Chaining

Dans le cas de BlindRead, l'INT 03, au lieu d'un ret, peut céder la place, quelques lignes plus loin à :

:00492D95 75DC           jne 00492D73        > Good Boy
:00492D97 B801000000     mov eax, 00000001   > Flag Bad Boy

Un Mov eax,00 à la place du mov eax,01 donnera le même résultat que le Ret, et plus " proprement " à mon avis…

Mais supposons que vous ayez une chaîne de per-thread handlers d'établie de telle façon que la fonction A appelle la fonction B qui appelle la fonction C :

Handler 1
  Fonction A -------- 
                     |
                     |
           Handler 2 |
                     |
                     V
                Fonction B -------- 
                                   |
                                   |
                         Handler 3 |
                                   |
                                   V
                              Fonction C



La pile ressemblera à ceci :

 

stack



3
rd




Stack



Frame

Use of stack by Function C

Handler 3

Local Data Function C



2
nd




Stack



Frame

Return address Function C

Use of stack by Function B

Handler 2

Local Data Function B



1
st




Stack



Frame

Return address Function B

Use of stack by Function A

Handler 1

Local Data Function A

 

Return address Function A

 

Stack

Ici, l'adresse de chaque fonction est poussée sur la pile.
Dans CodeSafe by Zhang De Hua, on trouve un anti Sice du même type que celui de BlindRead.

Une rapide recherche dans les codes du proggie va permettre de trouver :

UnhandledExceptionFilter et RtlUnwind

Le handler peut être initié en utilisant l'API RtlUnwind. Cette API est appelée ainsi :

PUSH Return value       
PUSH pExceptionRecord  
PUSH OFFSET CodeLabel 
PUSH LastStackFrame 
CALL RtlUnwind 

Bien que l'on trouve la présence de ces deux API dans les codes de la cible, seul RtlUnWind semble être sollicité...

* Referenced by a CALL at Addresses:
|:004034C2   , :OO40364A   , :O0403880
|
* Reference To: KERNEL32.RtlUnwind, Ord:OOOOh
|
:OO401268 FF25COC14AOO            Jmp dword ptr [O04AC1CO]

Voici l'un des call appelant :

:00403641 6AOO        push OOOOOOOO  > Return value
:00403643 50          push eax       > pExceptionRecord -> @71F9FC
:O0403644 684F364000  push OO40364F  > CodeLabel        -> good boy
:O0403649 52          push edx       > LastStackFrame   -> @ 71FBC4  Bad Boy
:OO40364A E819DCFFFF  Call KERNEL32.RtlUnwind
:O040364F 5B          pop ebx

On trouvera donc dans EDX l'adresse de la routine " bad boy "

Mais revenons à l'un de nos anti SoftIce :

La methode BCHK

Voici un cas de figure général, tel qu'emprunté à une routine de Pulsar :

S_BCHK proc

Push offset ExceptHandler2
PUSH FS:[0] 
MOV FS:[0],ESP      ;on installe l'except Handler qui sera ici notre ExceptHandler2

mov Flag,0
mov ebp,"BCHK"     ;on met le mot magique dans ebp
mov ax, 04h
int 3              ;on crée une erreur
                   ;à ce moment si sice est lancé on ne va rien voir et
                   ;tout va se passer normalement,
                   ;mais on n'atterrira pas dans l'except Handler...


mov eax,Flag       ;si sice est lancé, il est tjrs a Zero
mov Flag,0

POP FS:[0]
ADD ESP,4h         ;on enlève l'except handler

ret

ExceptHandler2:
mov Flag,01        ;Si on arrive ici c'est que sice n'est pas lancé
                   ;on met 1 dans le Flag -> pas de debugger
xor eax,eax
ret

S_BCHK endp

Même si vous avez désactivé l'utilisation de l'int 03h dans SoftIce (i3here off; faults off), vous ne pourrez pas passer cette routine!

Pourquoi?
Simplement parce qu'elle a été spécialement crée par les programmeurs de Softice pour permettre la communication avec d'autres produits Numega Boundschecker...

Un des aspects les plus embarrassant de cette technique, les premières fois qu'on la rencontre, c'est que rien ne semble anormal...
on vient de mettre 0 dans le Flag un peu avant, et c'est logique qu'il soit resté à zéro...
Par contre avec un peu d'expérience, on remarque facilement les codes types utilisés pour générer des erreurs et pour enclencher les Execpt Handlers...

Par exemple:

xor eax,eax
mov eax,[eax]       ;page Fault

Dans le cas de BlindRead, l'équivalent de cette routine est écrit ainsi :

:00492DA5 33D2                    xor edx, edx
:00492DA7 68C32D4900              push 00492DC3

On pousse sur la pile l'adresse de la routine (l'Except handler 2) :

:00492DC3 8B25D0A84A00            mov esp, dword ptr [004AA8D0]
on place l'adresse contenue dans la mémoire réservée [004AA8D0]
:00492DC9 8B2DD4A84A00            mov ebp, dword ptr [004AA8D4]
idem pour ebp avec [004AA8D4]
:00492DCF 5A                      pop edx > edx récupéré sur la pile
:00492DD0 646789160000            mov fs:[0000], edx
création d'une Except handler
:00492DD6 58                      pop eax
:00492DD7 33C0                    xor eax, eax
:00492DD9 C3                      ret
:00492DAC 64FF32        push dword ptr fs:[edx]
:00492DAF 8925D0A84A00  mov dword ptr [004AA8D0], esp
:00492DB5 892DD4A84A00  mov dword ptr [004AA8D4], ebp
:00492DBB 648922        mov dword ptr fs:[edx], esp > installation d'une except handler (edx=0)

:00492DDA B804000000    mov eax, 00000004
:00492DDF BD4B484342    mov ebp, 4243484B  > mot magique dans EBP
:00492DE4 CC            int 03             > création de l'erreur
:00492DE5 5A            pop edx            > on récupère EDX sur la pile
:00492DE6 646789160000  mov fs:[0000], edx > on installe l'Except Handler

Le fait d'avoir shunté les deux INT 03 en les remplaçant par des RET (ou en mettant le mov eax à 00) ne va plus permettre au programme de passer par ici :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00464548(U)
|
:00464564 0F010D2CAF4900          sidt [0049AF2C]
:0046456B A12EAF4900              mov eax, dword ptr [0049AF2E]
:00464570 83C008                  add eax, 00000008
:00464573 8B18                    mov ebx, dword ptr [eax]
:00464575 83C010                  add eax, 00000010
:00464578 8B00                    mov eax, dword ptr [eax]
:0046457A 25FFFF0000              and eax, 0000FFFF
:0046457F 81E3FFFF0000            and ebx, 0000FFFF
:00464585 2BC3                    sub eax, ebx
:00464587 83F81E                  cmp eax, 0000001E
:0046458A 7406                    je 00464592
:00464590 33C0                    xor eax, eax
:00464592 5A                      pop edx
:00464593 646789160000            mov fs:[0000], edx
:00464599 5A                      pop edx
:0046459A C3                      ret

Et comme il y a deux INT 03, on trouve de la même façon une seconde SIDT en 00492E7C, à l'identique de la première
Le rôle de SIDT est la sauvegarde du registre de la table des interruptions.
On peut trouver une telle routine, bien que rarement, lors de la recherche de SoftIce comme a pu le démontrer Pulsar dans l'une de ses études :

sidt  fword ptr IDT                      ;on obtient l'IDT
mov   ebx, dword ptr [IDT+2]             ;ebx=IDT base
add   ebx, 8*ExceptionUsed               ;ebx=selector pour l'int recherchee
cli                                      ;clear interupts

                                         ;On sauvegarde l'ancienne routine
mov   dx, word ptr [ebx+6]               ;le highword
shl   edx, 16d
mov   dx, word ptr [ebx]                 ;le lowword
mov   [OldGate], edx
mov   eax, Fct_Address                   ;on installe notre routine
mov   word ptr [ebx], ax                 ;le lowword
shr   eax, 16d
mov   word ptr [ebx+6], ax               ;le highword

int   ExceptionUsed                      ;on passe en ring 0 :)))
  
mov   ebx, dword ptr [IDT+2]             ;on restore la vielle routine
add   ebx, 8*ExceptionUsed
mov   edx, [OldGate]
mov   word ptr [ebx], dx
shr   edx, 16d
mov   word ptr [ebx+6], dx

ret

On a donc une fonction parfaitement opérationnelle prenant comme paramètre d'entrée l'offset de la routine que nous voulons utiliser en ring0.
On l'utilisera alors comme suit

push offset Ring0Proc
call RingZero 
Ring0Proc:
mov eax,dr7                         ;par exemple :)

bx=1350
ax=1360

Faites bx-ax-10 et si le résultat est égal à zéro sice n'as pas hooké les int, et donc il est absent :)
La fonction renvoie ax=0 si sice est absent et une autre valeur s'il est là. :)

Pour autant le RET qui remplace l'INT 03 est un morceau de chance. Suite à la lecture de ce texte, TeeJi m'a apporté ce complément indispensable à la compréhension de la façon dont l'exception fonctionne:

Je vais recommencer l'analyse de la routine BCHK utilisée par Blind Read et essayer d'expliquer pourquoi un simple RET n'aurait pas du être suffisant:

* Referenced by a CALL at Address:
|:004643CA
:0046448D 33D2                    xor edx, edx
:0046448F 68AB444600              push 004644AB
:00464494 64FF32                  push dword ptr fs:[edx]
:00464497 892524AF4900            mov dword ptr [0049AF24], esp
:0046449D 892D28AF4900            mov dword ptr [0049AF28], ebp
:004644A3 648922                  mov dword ptr fs:[edx], esp
:004644A6 EB1A                    jmp 004644C2
:004644A8 90                      nop
:004644A9 90                      nop
:004644AA 90                      nop
:004644AB 8B2524AF4900            mov esp, dword ptr [0049AF24]
:004644B1 8B2D28AF4900            mov ebp, dword ptr [0049AF28]
:004644B7 5A                      pop edx
:004644B8 646789160000            mov fs:[0000], edx
:004644BE 58                      pop eax
:004644BF 33C0                    xor eax, eax
:004644C1 C3                      ret  

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004644A6(U)
|
:004644C2 B804000000              mov eax, 00000004
:004644C7 BD4B484342              mov ebp, 4243484B
:004644CC CC                      int 03
:004644CD 5A                      pop edx
:004644CE 646789160000            mov fs:[0000], edx
:004644D4 5A                      pop edx
:004644D5 B801000000              mov eax, 00000001
:004644DA C3                      ret

Ce qui est surprennant, la première fois qu'on voit cela sous Softice, c'est que quand on trace sur l'INT 03, le programme continue comme si de rien était, sans jamais redonner la main à Sice. Donc, il va falloir émuler et/ou forcer l'Exception. Mais avant, il faut bien comprendre chaque morceau de la fonction copiée ci-dessus.

La première partie installe un SEH :

:0046448D 33D2          xor edx, edx                   ;edx = 0
:0046448F 68AB444600    push 004644AB                  ;@ de l'exeption handler
:00464494 64FF32        push dword ptr fs:[edx]        ;@ de la structure err précédente

:00464497 892524AF4900  mov dword ptr [0049AF24], esp  ;sauve l'esp courant
:0046449D 892D28AF4900  mov dword ptr [0049AF28], ebp  ;sauve l'ebp courant

:004644A3 648922        mov dword ptr fs:[edx], esp    ;set le SEH ( edx = 0 => fs:[0] )

J'ai divisé cette sous-fonction en 3 parties :

1. la première créé la structure ERR.

Cette structure est définie ainsi :

 1er  dword +0       Pointeur vers la structure ERR suivante
 2eme dword +4     Pointeur vers l'exception handler

Mais cette structure n'est pas limitée, on peut très bien lui rajouter un 3ème dword, un 4ème, etc.. contenant du data que nous voudrions passer au SEH. Quand le per-thread handler est appelé, ESP pointe vers une structure définie comme suit :

 ESP+4 Pointeur vers une structure EXCEPTION_RECORD
 ESP+8 Pointeur vers la structure ERR correspondant a l'expetion handler
 ESP+C Pointeur vers une structure CONTEXT

On peut donc récupèrer le data rajouté à la structure ERR en faisant :

mov eax, [esp+8]
add eax, 8

A ce moment, EAX pointe sur le 3ème dword de la structure ERR.

2. la seconde sauve ESP et EBP

Pourquoi sauver ces 2 registres ? On verra un peu plus loin que l'ont n'est pas obligé de quitter l'handler par la voie normale. Je m'explique, en temps normal, un Exption handler doit traiter l'exception. S'il arrive à la traiter, il RETourne avec 0 dans eax et le thread reprend son exécution à l'address pointée par le variable RegEIP de la structure CONTEXT envoyée en paramètre par le registre ESP à l'handler. S'il n'arrive pas à la traiter, il renvoit 1 dans EAX et c'est l'handler suivant dans la chaine des structures ERR, ou à défaut le Final Exception Handler qui va essayer de traiter l'exception (tout cela étant géré par une fonction interne à Windows ). Mais on n'est pas obligé de quitter la procédure ainsi, on peu aussi envoyer un ESP / EBP sure à l'handler, qui va récupérer ces 2 valeurs, et faire un simple RET. et le programme va continuer son exécution à l'adresse pointée par [esp]. ( comme normal ). Un exemple :
 

start:

    Call Exemple1           ;Appel la procédure Exemple1
    invoke ExitProcess, 0   ;Termine le programme

    Exemple1 proc

        push esp            ;Créé une structure ERR
        push ebp
        push ADDR MyEHandler
        push FS:[0]
        mov FS:[0], esp     ;Set le SEH

        xor eax, eax       ;Créé une Exception Divide by 0
        div eax

        MyEHandler:
            mov esp, [esp+8];Se positionne sur le 3eme dword de la structure ERR
            add esp, 8
            pop ebp         ;Récupère l'EBP qui était dans la structure ERR dans EBP
            pop esp         ;idem pour ESP
        ret

    Exemple1 endp

end start

Donc ce mini-prgramme ne fait rien du tout à par une Exception Divide By 0. La première partie de la procédure Exemple1 va créer une structure ERR ainsi :

ERR+0     NextERR
ERR+4     ADDR MyEHandler
ERR+8     EBP
ERR+C     ESP

et lorsqu'on met ESP sur la pile (dans la structure ERR), [ESP] est l'address de retour de la procédure Exemple1. Quand l'exception est créé, notre Handler restaure les registres ESP et EBP dans l'état qu'ils avaient au début de la procédure Exemple1 et donc [ESP] pointe sur l'address de retour de Exemple1. Le RET va nous emmener non plus dans la fonction de gestion des Exception's de Windows, mais juste àprès le Call Exemple1 et le programme va continuer sont exécution comme si la procédure Exemple1 c'était terminée normalement.

Rem : contrairement à ce que j'ai déjà entendu, un SEH n'est pas exécuté en Ring0 !!! Sinon cette méthode ne serait pas possible, car le programme continuerait sont exécution en Ring0 --> plantage !

Si vous n'avez pas bien compris ce point 2, je vous conseil de le relire et d'essayer de bien l'asimiler car la protection de Blind Read est basée sur ce principe !

3. Set le SEH

En fait, quand un Thread est crée, il y a plusieurs informations sur celui-ci qui sont sauvées dans une structure TIB (Thread Information Bloc) et cette structure TIB se trouve en fs:[0]. Cette Structure fait partie de la structure Thread Database qui contient toutes les informations nécessaires à l'exécution du Thread. Le premier dword du TIB est un pointeur vers une structure ERR définissant le premier per-thread SEH de la chaine. Lorsqu'il y a une Exception, Windows rentre dans une boucle du type de celle-ci :

    mov eax, fs:[0]     ;met le pointeur vers la première structure ERR de la chaine dans EAX

NextERR:

    cmp dword ptr[eax], -1      ;regarde si il y a encore une structure ERR ( -1 si il y en a pas )
    je Final_Exception_Handler  ;si non, il exécute le Final_Exception_Handler ( Unhandled one )

    push eax                    ;sauve EAX

    push pContext               ;ESP+C est un pointeur sur une structure CONTEXT
    push eax                    ;ESP+8 est le pointeur sur la structure ERR correspondante
à l'handler

    push pExceptionRecord       ;ESP+4 est le pointeur vers l'exception la structure
EXCEPTION_RECORD

    call dword ptr[eax+4]       ;et on appel l'Exception Handler de la structure ERR.
    test eax, eax               ;Test la valeur de retour
    pop eax                     ;restaure EAX qui pointe vers la structure ERR courante
    jz RetournToThread          ;Si la fonction renvoit 0 c'est que l'handler a traité l'exception
                                ;et l'exécution du Thread peu continuer

    mov eax, [eax]              ;sinon on met dans eax le pointeur vers la structure ERR suivante
    jmp NextERR                 ;et on boucle.

Evidemment, cette fonction n'est pas celle de Windows, mais c'est pour vous donner une idée simpliste du principe.

Donc, si on revient à la 1ere partie de la fonction de Blind Read

:0046448D 33D2          xor edx, edx                   ;edx = 0
:0046448F 68AB444600    push 004644AB                  ;@ de l'exeption handler
:00464494 64FF32        push dword ptr fs:[edx]        ;@ de la structure err précédente

:00464497 892524AF4900  mov dword ptr [0049AF24], esp  ;sauve l'esp courant
:0046449D 892D28AF4900  mov dword ptr [0049AF28], ebp  ;sauve l'ebp courant

:004644A3 648922        mov dword ptr fs:[edx], esp    ;set le SEH ( edx = 0 ==> fs:[0] )
:004644A6 EB1A          jmp 004644C2                   ;saute directement à la partie 3

La structure ERR est minimaliste, juste l'address de l'handler et celui de la structure ERR suivante ( 2 dwords ). Ensuite, ESP et EBP sont sauvés dans des variable globale et le SEH est installé. Pour la suite du cours, j'appellerai [0049AF24] [ESP_Backup] et l'autre [EBP_Backup] pour une meilleur compréhension. On peut maintnant reguarder la deuxième partie de la fonction :

:EHANDLER

8B2524AF4900    mov esp, dword ptr [ESP_Backup]  ;restaure la valeur de ESP et EBP

8B2D28AF4900    mov ebp, dword ptr [EBP_Backup]  ;[esp] est le pointeur vers la structure ERR

:004644B7 5A              pop edx            ;le ptr vers la struc ERR suivante dans EDX
:004644B8 646789160000    mov fs:[0000], edx ;Set le SEH avec la struc pointée par EDX
                                             ;il enlève donc le SEH crée par la 1ere partie
:004644BE 58              pop eax            ;l'address de l'handler dans EAX
                                             ;sert uniquement a restaurer la pile
:004644BF 33C0            xor eax, eax       ;met EAX à 0
:004644C1 C3              ret                ;retourne

On voit donc que comme j'avais expliqué au point deux un peu plus haut, cet handler ne quitte pas par la methode normal, il reprend un esp/ebp sure et retourne comme si c'était une fonction normal. Reguardons enfin la 3ème partie :

:004644C2 B804000000      mov eax, 00000004  ;met 4 dans EAX
:004644C7 BD4B484342      mov ebp, 4243484B  ;met BCHK dans EBP
:004644CC CC              int 03             ;créé une exception BreakPoint
:004644CD 5A              pop edx            ;le ptr vers la struc ERR suivante dans EDX
:004644CE 646789160000    mov fs:[0000], edx ;Set le SEH avec la struc pointée par EDX
                                             ;il enlève donc le SEH crée par la 1ere partie
:004644D4 5A              pop edx            ;l'address de l'handler dans EDX
                                              ;sert uniquement a restaurer la pile
:004644D5 B801000000      mov eax, 00000001  ;met 1 dans EAX
:004644DA C3              ret                ;retourne

Donc, l'INT 03 est appelé avec BCHK, donc Softice ne va pas créé d'exception, et le programme va continuer sont exception. Si Softice n'est pas chargé, l'exception va avoir lieu, et l'exception handler en de la structure ERR en fs:[0] va être appelé. Cette handler c'est la 2eme partie que j'ai analysé ci-dessus. Cette fonction va donc retourner avec 1 dans EAX si Softice est chargé et 0 dans le cas contraire.

On voit qu'au moment ou l'INT 03 est exécuté, ESP pointe toujours vers la structure ERR fraichement crée. Vu que l'ExceptionHandler n'utilise pas les structures CONTEXT, ERR et EXCEPTION_RECORD, on peut emuler très facilement l'exception, pour cela, nous allons simplement faire un CALL ou un JMP vers l'Exception Handler. On sait que ESP pointe vers la structure ERR. dans l'address de l'handler est en ERR+4, soit en [ESP+4] et donc, il suffit de remplacer l'INT 03 par

add esp, 04    ;ESP pointe alors sur ERR+4
ret            ;et on saute en ERR+4 et donc sur l'handler.

Dans ton cours, Christal, tu as simplement modifier le INT 03 par un RET. Alors faudrait maintenant comprendre pourquoi ca a fonctionner ? La raison est toute simple, c'est que tu as sauté sur la pile, l'EIP est donc devenu l'address de ta structure ERR et donc, c'est le code représenté par le contenu de cette structure qui a été exécuté. Evidemment, c'est pas censé être du code, et il y a donc eu une Exception qqconque mais il se pourrait très bien que le code représenté ne fasse pas d'exception et que ton code à ce moment là boucle infiniment, ou je ne sais pas tout ce qu'il peut faire. Donc, ta modification fonctionne dans ce cas-ci, tant mieux, mais ce n'est pas correct.

Maintenant, imaginez que le programme soit comme celui que j'avais écris plus haut Exemple1:

start:

    Call Exemple1           ;Appel la procédure Exemple1
    invoke ExitProcess, 0   ;Termine le programme

    Exemple1 proc

        push esp            ;Créé une structure ERR
        push ebp
        push ADDR MyEHandler
        push FS:[0]
        mov FS:[0], esp     ;Set le SEH

        mov ebp, 'KHCB'     ;Créé une Exception BreakPoint avec ebp=BCHK
        mov eax, 4
        int 03

        pop eax             ;Enlève le SEH et retourne avec EAX=1
        mov dword ptr fs:[0], eax
        pop eax
        xor eax, eax
        inc eax
        ret

        MyEHandler:
            mov esp, [esp+8];Se positionne sur le 3eme dword de la structure ERR
            add esp, 8
            pop ebp         ;Récupère l'EBP qui était dans la structure ERR dans EBP
            pop esp         ;idem pour ESP

            pop eax         ;Enlève le SEH et retourne avec EAX=0
            mov dword ptr fs:[0], eax
            pop eax
            xor eax, eax
        ret

    Exemple1 endp

end start

A ce moment, là, si vous remplacer le INT 03 par un ADD ESP, 04 / RET, il y aura une erreur. Vous devinez pourquoi ? Simplement parce que MyEHandler fait référence à la structure ERR passée normallement en paramètre. Si vous patchez ainsi, la Structure ERR ne se trouvera pas en [esp+8] et il y aura donc une erreur. Dans un tel cas, 2 solutions s'offrent à vous, soit vous mettez vous-même les information sur la pile avant d'appeler la fonction, et dans ce cas, je vous souhaite grand plaisir, soit vous forcez l'Exception. Comment la forcer ? C'est certainement une bonne question! Il faut la forcer en utilisant une Exception qui ne sera pas arrêtée par Softice, soit une Divide by 0, soit un accés memoire non authorisé, etc.. Par exemple, vous pouvez remplacer le INT 03 par XOR EAX, EAX / DIV AX. A ce moment, il y aura bien une Exception et Windows va se charger pour vous de mettre les infos sur la pile et d'appeler l'handler. Maintenant, voyons comment compliquer un peu les chose, et imaginez que MyEHandler soit codé comme suit :

    MyEHandler:
        mov ebx, [esp+4]
        assume EBX:PTR EXCEPTION_RECORD
        xor eax, eax
        cmp [ebx].ExceptionCode, EXCEPTION_BREAKPOINT
        jz @B
        inc eax
       @@:
        mov esp, [esp+8]
        add esp, 8
        pop ebp
        pop esp
        ret

Là, nous avons un problème... Car si on force l'Exception avec une autre Exception, l'Handler va renvoyer 1 dans EAX ==> bad guy ! Donc nous ne pouvons pas forcer ainsi l'Exception. A Propos, ceci est une méthode utilisée pour la détection de Frogsice qui force l'Exception par un Divide by 0.

Mais notre problème à présent, c'est d'arriver à simuler une Exception INT 03.

Rem : ce qui suit n'est que théorique, je n'ai jamais testé cette méthode.

Et là, je dis vive Microsoft ! :) Un petit coup d'oeil dans la références des APIs et on trouve ceci :

The RaiseException function raises an exception in the calling thread.

VOID RaiseException(

    DWORD dwExceptionCode, // exception code
    DWORD dwExceptionFlags, // continuable exception flag
    DWORD nNumberOfArguments, // number of arguments in array
    CONST DWORD *lpArguments  // address of array of arguments
   );  

Il suffit donc de remplacer le INT 03 par

invoke RaiseException, EXCEPTION_BREAKPOINT, NULL, NULL, NULL

Je n'ai jamais testé cette méthode.. je ne peux dès lors pas vous dire si elle fonctionne.. mais théoriquement, elle devrait. J'attends un test de votre part !

L'autre solution dans un tel cas, c'est de bien comprendre les éléments utilisés ( ici, la structure ERR et l'ExceptionCode de la structure EXCEPTION_RECORD ) et de ne remplir que ces structures-là et appeler soit-même l'Exception... Mais encore une fois, il y aura un problème si l'Handler retourne par la voit normal ( la fonction de gestion des EH de Windows ). Donc à ce moment, il faut modifier soit MyEHandler pour qu'il renvoit une bonne valeur (dans le cas de mon programme Exemple, c'est EAX=0), soit modifier la fonction pour qu'elle renvoit une bonne valeur. Dans mon exemple, il faudrait soit nopper l'INC EAX de MyEHandler et le INC EAX de la procédure Exemple, soit mettre un XOR EAX, EAX / RET à l'EntryPoint de la fonction car dans le fond, cette fonction ne sert a rien. Donc, si on applique cela à la fonction de Blind Read, nous avons à présent 4 manières de patcher :

    1.

:004644CC                         add esp, 04  ;Emule l'exception
                                  ret

    2.

:004644CC                         xor eax, eax  ;Force l'exception
                                  div ax

    3.

:0046448D                         xor eax, eax  ;renvoit toujours EAX=0
                                  ret

    4.

:004644D5                         mov eax, 00000000  ;idem

Rem : Une cinquième possibilitée serait de faire

    5.

:004644CC                         push 0  ;Raise l'exception
                                  push 0
                                  push 0
                                  push 80000003  ;EXCEPTION_BREAKPOINT
                                  call RaiseException

Maintenant, je vais vous montrer comment tracer un programme qui utilise de telles Exceptions. Exemple : BlindRead évidemment.
Sous Softice, on arrive facilement à la fonction de détection par BCHK/INT03 dont revoici le code :

0177:0046448D  33D2                XOR       EDX,EDX
0177:0046448F  68AB444600          PUSH      004644AB
0177:00464494  64FF32              PUSH      DWORD PTR FS:[EDX]
0177:00464497  892524AF4900        MOV       [0049AF24],ESP
0177:0046449D  892D28AF4900        MOV       [0049AF28],EBP
0177:004644A3  648922              MOV       FS:[EDX],ESP
0177:004644A6  EB1A                JMP       004644C2
0177:004644A8  90                  NOP
0177:004644A9  90                  NOP
0177:004644AA  90                  NOP
0177:004644AB  8B2524AF4900        MOV       ESP,[0049AF24]
0177:004644B1  8B2D28AF4900        MOV       EBP,[0049AF28]
0177:004644B7  5A                  POP       EDX
0177:004644B8  646789160000        MOV       FS:[0000],EDX
0177:004644BE  58                  POP       EAX
0177:004644BF  33C0                XOR       EAX,EAX
0177:004644C1  C3                  RET
0177:004644C2  B804000000          MOV       EAX,00000004
0177:004644C7  BD4B484342          MOV       EBP,4243484B
0177:004644CC  CC                  INT       3
0177:004644CD  5A                  POP       EDX
0177:004644CE  646789160000        MOV       FS:[0000],EDX
0177:004644D4  5A                  POP       EDX
0177:004644D5  B801000000          MOV       EAX,00000001
0177:004644DA  C3                  RET

Lorsqu'on se trouve sur 0177:004644CC, une Exception doit avoir lieu. Cette Exception n'aura lieu que si vous tracez avec F10. Sinon, Softice ne créera pas d'Exception. Donc, nous allons mettre un BreakPoint sur l'Handler. Dans ce cas-ci, c'est très simple car l'installation du SEH se trouve just au-dessus et on voit directement que l'handler est en 004644AB. Mais bon, faisons comme si nous ne savions pas. Pour retrouver son adresse, nous faisons :

:dd fs:0

1A0F:00000000 0071:FC00 0072:0000 0071:B000 40B7:215E     ..q...r...q.^!.@
1A0F:00000010 0000:0000 0000:0000 8174:517C FFFF:0001     ........|Qt.....
1A0F:00000020 0000:0000 D75F:60D1 0000:220F 8174:5204     .....`_.."...Rt.
1A0F:00000030 8174:40E8 8000:6498 ????:???? ????:????     .@t..d..........

Ce dump de la mémoire est en fait le TIB correspondant au Thread de BlindRead. Si vous avez bien compris tout ce cours, vous savez dès lors que le 1er dword : 0071FC00 est le pointeur vers la 1ere structure ERR de la chaine.

:dd ds:0071fc00

017F:0071FC00 0071FC44  004644AB  004643CF  00C72B4C      D.q..DF..CF.L+..
017F:0071FC10 00C72B4C  0071FC18  00C79010  00C6DFE4      L+....q.........
017F:0071FC20 00463E38  00000003  004635AF  41DE0D44      8>F......5F.D..A

Il est impossible de savoir quelle est la grandeur de cette structure, mais nous savons que le 1er dword est le pointeur vers la structure ERR suivante et que le second dword est l'address de l'handler.

ERR+0   0071FC44
ERR+4   004644AB
ERR+.    ...

Nous pouvons à présent mettre, par exemple un BPX sur ce handler et aller voir son code pour savoir quel est la méthode à utiliser pour contourner cette détection :

:bpx *(0071fc00+4)

:u 004644ab

0177:004644AB  8B2524AF4900        MOV       ESP,[0049AF24]
0177:004644B1  8B2D28AF4900        MOV       EBP,[0049AF28]
0177:004644B7  5A                  POP       EDX
0177:004644B8  646789160000        MOV       FS:[0000],EDX
0177:004644BE  58                  POP       EAX
0177:004644BF  33C0                XOR       EAX,EAX
0177:004644C1  C3                  RET

Ce handler ne fait usage d'aucune des structures qui lui sont passées en paramètres et ne retournant pas par la porte normale, nous pouvons donc émuler avec un ADD ESP, 04 / RET l'exception. Nous pourrions aussi mettre un XOR EAX, EAX / DIV AX et alors notre Breakpoint nous servirait à qqchose.

:a EIP
    :xor eax, eax
    :div ax
    :

:bl

03)   BPX #017F:004644AB

un appuis sur F5 devrait vous amener tout droit à l'Exception Handler. Maintenant, imaginons que vous voudriez mettre un BPX sur tous les Exception Handler, il suffit de faire

:dd ds:0071fc00

017F:0071FC00 0071FC44  004644AB  004643CF  00C72B4C      D.q..DF..CF.L+..

:bpx 004644AB
:dd ds:0071FC44

017F:0071FC44 0071FC84  00463942  0071FC64  00000010      ..q.B9F.d.q.....

:bpx 00463942
:dd 71fc84

017F:0071FC84 0071FC90  0040F8AF  0071FCA8  0071FCB0      ..q...@...q...q.

ainsi de suite. On remarque qu'il y une longue liste chainée. Voici le liens entre les tructures ERR suivante :

017F:0071FC90 0071FCB0  0040F8CE
-> 017F:0071FCB0 0071FCBC  00446884
--> 017F:0071FCBC 0071FCC8  004468BC
---> 017F:0071FCC8 0071FCD4  004468DC
----> 017F:0071FCD4 0071FE04  004032AD
-----> 017F:0071FE04 0071FE2C  0044D772
------> 017F:0071FE2C 0071FF68  00403840
-------> 017F:0071FF68 FFFFFFFF  <-- -1 = Fin de la liste chainée

Pour terminer, Une manière d'automatiser tout ca, on peut utiliser une macro sous Softice qui va mettre un BPX sur le premier SEH a chaque fois qu'il est modifié. Ainsi, on bloquera a chaque fois qu'il y a une Exception.

:bpmd fs:0 W if eip<80000000 do "bc 01;bpx *(esp+4);x;"

Donc il met un BreakPoint on memory Write Acces en fs:0. le IF eip<80000000 c'est pour ne bpxé que les SEH du process ( et pas ceux de USER32 / Kernel32 ). et lorqu'il y a accés en écriture, il enlève le BP précédent et en met un nouveau sur l'address de l'handler dans la nouvelle structure ERR ( supposée en [ESP] ). Donc vous mettez ca au début du prog, il ne s'arretera que lorsqu'il y aura une Exception. Je ne vous conseille pas de le faire dès le début.. mais vous pouvez toujours essayer de l'utiliser lorsque vous vous situez dans la routine anti-sice.

Pour terminer, il faudrait parler de RtlUnwind. Cette fonction consiste, en gros, à rétablir la pile après l'exécution d'une suite d'Exception imbriquée. Par exemple, si le 1er handler ne gère pas l'exception, il la passe au second qui pourrait la passer au suivant qui traite l'erreur et retourne à une Safe Place ou le seul SEH qu'il doit y avoir, c'est le 3eme. Dans un tel cas, on utilise RtlUnwind. On pourrait évidemment écrire sa propre fonction d'Unwind mais avant cela, il faudrait connaître le principe de cette fonction :

    Tout d'abord, elle construit un Context avec le regEip égal à l'address de retour donné en paramètre. Ensuite elle construit une structure ExceptionRecord à partir de celle donnée en paramètre et met l'ExceptionFlags à 2 ( EH_UNWINDING ). Après cela, elle va appeler chaque EHandler de la chaine en commencant par celui en fs:[0]. Après chaque exécution, elle va l'enlever de la chaine... jusqu'au moment ou elle arrivera à l'address de la structure ERR passée en paramètre à la fonction. Pour illustrer le principe, j'ai écris un petit programme qui ne fait encore rien à part des Exception ( la source complète se trouve ici ):


start:

    push offset MyFinalEHandler  ;Set SEH
    push fs:[0]
    mov fs:[0], esp

    mov FinalERRStruct, esp      ;Save SafeStackframe
    mov SafeESP, esp
    mov SafeEBP, ebp

    mov LastExceptionCode, NO_EXCEPTION

    call Except1                 ;Call Exception Fonction

   SafePlace:

    ;------------------------------------------------------------------------------------------
    ; Ici vous pouvez mettre du code qui s'exécutera normalement sans problème
    ; ou tout au moin indépendemment des exceptions qui seraient parvenues précédemment
    ;------------------
    ; Pour l'exemple, lorsqu'une Exception survient, sont code est sauvée dans
    ; LastExceptionCode si c'est pas lors de l'Unwind. Le SafeCode va tester et afficher le
    ; contenu de LastExceptionCode.
    ;------------------------------------------------------------------------------------------

    mov eax, LastExceptionCode
    .if eax==NO_EXCEPTION
     invoke MessageBoxA, 0, ADDR szNoException, ADDR szLastException, 0
    .elseif eax==EXCEPTION_INT_DIVIDE_BY_ZERO
     invoke MessageBoxA, 0, ADDR szDivideBy0, ADDR szLastException, 0
    .elseif eax==EXCEPTION_ACCESS_VIOLATION
     invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szLastException, 0
    .endif

    pop dword ptr fs:[0]         ;Remove SEH
    add esp, 4

    invoke ExitProcess, NULL     ;Exit Process

    ;----------------------------------------------
    ; Si l'exception n'a pas été gèrée, ExitProcess
    ;----------------------------------------------
    MyFinalEHandler:
        invoke ExitProcess, 0


La première partie du programme Set un SEH qui sera dès lors le dernier EH exécuté avant de passer la main au UnhandledExceptionFilter ( je n'explique pas sont utilisation vu qu'elle est très bien expliquée dans la Refs des API Win32 ). Ensuite, vu qu'on se trouve dans un endroit Safe, il sauve le StackFrame ( EBP / ESP ) ainsi que l'address de notre Structure ERR Finale pour donner en paramètre plus tard à RtlUnwind. LastExceptionCode recevra le code de la dernière exception survenue et est donc initialisée à NO_EXCEPTION ( -1 ). Le Handler de notre FinalERRStruct va simplement terminer le Process car si on arrive à lui, c'est qu'aucun de nos EHandler n'a gèré l'exception. La SafePlace se trouve juste après l'appel de la fonction supposée à risque Except1 dont voici le code :


    ;------------------------------------------------------------------------
    ; Except1 PROC - Supposons qu'elle utilise une zone mémoire à risque
    ;------------------------------------------------------------------------
    ; Install un SEH qui gère les exception Access Violation
    ; Et appel la fonction Except2
    ;------------------------------------------------------------------------
    Except1 proc

        push offset @@EHandler   ;Set SEH
        push fs:[0]
        mov fs:[0], esp

        call Except2             ;Call next Except Func

        pop dword ptr fs:[0]     ;Remove SEH
        add esp, 4
        ret

        ;----------------------------------------------
        ; Gère uniquement les violations mémoire
        ;----------------------------------------------
        @@EHandler:
            mov eax, [esp+04]
            assume eax:PTR EXCEPTION_RECORD
            test [eax].ExceptionFlags, 2
            jnz @F
            push [eax].ExceptionCode
            pop LastExceptionCode

           @@:
            cmp [eax].ExceptionCode, EXCEPTION_ACCESS_VIOLATION
            jz @F
            mov eax, EXCEPTION_EXECUTE_HANDLER
            ret

           @@:
            invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szException, 0

            ;RtlUnwind va enlever les SEH jusqu'au FinalERRStruct dans la chaine
            ;          et appelera chaque Handler enlevé avec comme paramètre
            ;          ExceptionFlag = EH_UNWINDING

            mov eax, esp
            push NULL              ;Return Value  // unused
            add eax, 0Ch
            push dword ptr[eax]    ;pExceptionRecord
            push offset @F         ;Return Addr
            sub eax, 4
            push FinalERRStruct    ;pErrStruct
            call RtlUnwind         ;Unwind

           @@:

            ; Ici, je propose 2 manière de se retrouver à la SafePlace...
            ; soit par la petite porte en restaurant le StackFrame
            ; soit par la voie normale en modifiant le Context
            ;
            ; Rem : Une autre possibilitée aurait été de laisser le SafePlace
            ; ---   Restaurer un SafeStackFrame et de mettre l'offset SafePlace
            ;       comme address de retour de RtlUnwind.

;           jmp @@LittleDoor
            jmp @@NormalWay
 

           @@LittleDoor:
            mov ebp, SafeEBP       ;Set SafeStackframe
            mov esp, SafeESP
            push SafePlace         ;Jmp to SafePlace
            ret

           @@NormalWay:
            mov eax, [esp+0Ch]
            assume eax:PTR CONTEXT
            push SafeEBP            ;Restaure un SafeStackFrame
            pop [eax].regEbp
            push SafeESP
            pop [eax].regEsp
            push offset SafePlace   ;Modifie l'EIP
            pop [eax].regEip

            mov eax, EXCEPTION_CONTINUE_SEARCH    ;On a réparé l'erreur... essayons de rééxécuter le Thread.
            ret

    Except1 endp


Commencons par une remarque de syntaxe, l'utilisation des double-arobas permet de définir des label local. En d'autres mots, si vous mettez @@ devant un label, celui-ci est indépendant des autres fonctions du programme. De plus, le symbol @F correspond au prochain label local dans la source et @B correspond au précédent. ( Backward / Forward ).
La fonction Except1 commence par installer un SEH avant d'exécuter une autre fonction à risque : Except2. Le handler du SEH de Except1 est le plus important et je vais analyser chaque partie avec vous :

        @@EHandler:
            mov eax, [esp+04]
            assume eax:PTR EXCEPTION_RECORD
            test [eax].ExceptionFlags, 2
            jnz @F
            push [eax].ExceptionCode
            pop LastExceptionCode

           @@:

Cette première partie sauve l'ExceptionCode dans LastExceptionCode si l'ExceptionFlag est différent de 2 ou plus précisément si l'Handler n'est pas appelé lors de l'Unwind.

            cmp [eax].ExceptionCode, EXCEPTION_ACCESS_VIOLATION
            jz @F
            mov eax, EXCEPTION_EXECUTE_HANDLER
            ret

           @@:

Cet handler ne gère que les exception de violation mémoire et donc, si ce n'est pas une telle exception, il passe la main au prochain SEH dans la chaine, soit l'Handler de notre FinalERRStruct.

            invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szException, 0

            ;RtlUnwind va enlever les SEH jusqu'au FinalERRStruct dans la chaine
            ;          et appelera chaque Handler enlevé avec comme paramètre
            ;          ExceptionFlag = EH_UNWINDING

            mov eax, esp
            push NULL              ;Return Value  // unused
            add eax, 0Ch
            push dword ptr[eax]    ;pExceptionRecord
            push offset @F         ;Return Addr
            sub eax, 4
            push FinalERRStruct    ;pErrStruct
            call RtlUnwind         ;Unwind

           @@:

Il affiche ensuite l'erreur dans une MessageBox et appel RtlUnwind. RtlUnwind recoit en paramètre le pointeur vers l'ExceptionRecord passé en paramètre à l'EHandler et l'address de retour est juste après le call RtlUnwind. RtlUnwind va ensuite appeler chaque EHandler jusqu'au moment ou elle arrivera à notre FinalERRStruct qui redeviendra le premier SEH de la chaine.

;           jmp @@LittleDoor
            jmp @@NormalWay
 

           @@LittleDoor:
            mov ebp, SafeEBP       ;Set SafeStackframe
            mov esp, SafeESP
            push SafePlace         ;Jmp to SafePlace
            ret

           @@NormalWay:
            mov eax, [esp+0Ch]
            assume eax:PTR CONTEXT
            push SafeEBP            ;Restaure un SafeStackFrame
            pop [eax].regEbp
            push SafeESP
            pop [eax].regEsp
            push offset SafePlace   ;Modifie l'EIP
            pop [eax].regEip

            mov eax, EXCEPTION_CONTINUE_SEARCH    ;On a réparé l'erreur... essayons de rééxécuter le Thread.
            ret

    Except1 endp

La je vous laisse le choix de la méthode à utiliser pour revenir à la SafePlace. Soit vous restaurez le StackFrame manuellement et sautez à l'address de la SafePlace, soit vous modifier le Context du Thread et vous essayez de relancer l'exécution ( en renvoyant EXCEPTION_CONTINUE_SEARCH dans eax ). Allons voir maintenant le code de la seconde fonction à risque :


    ;------------------------------------------------------------------------
    ; Except2 PROC - Supposons qu'elle utilise des divisions d'entier ( DIV )
    ;------------------------------------------------------------------------
    ; Install un SEH qui gère les exception Divide By Zero
    ; Et créé soit une Exception Divide By Zero
    ;         soit une Exception Access Violation
    ;------------------------------------------------------------------------
    Except2 proc

        push esp                   ;SetSEH
        push ebp
        push offset @@EHandler
        push fs:[0]
        mov fs:[0], esp

        ;---------------------------------------------
        ; Mettez un ';' devant l'exception que vous ne
        ; voulez pas créer.
        ;---------------------------------------------

        xor eax, eax
;       div ax                     ;Raise a Divide by Zero Exception
        mov dword ptr[eax], eax    ;Raise a Access Violation Exception

        pop dword ptr fs:[0]       ;Remove SEH
        add esp, 0Ch
        ret

        ;----------------------------------------------
        ; Gère uniquement les divisions par 0
        ;----------------------------------------------
        @@EHandler:
            mov eax, [esp+4]
            assume eax:ptr EXCEPTION_RECORD
            test [eax].ExceptionFlags, 2
            jnz @F
            push [eax].ExceptionCode
            pop LastExceptionCode

           @@:
            cmp [eax].ExceptionCode, EXCEPTION_INT_DIVIDE_BY_ZERO
            jz @F
            mov eax, EXCEPTION_EXECUTE_HANDLER
            ret

           @@:
            invoke MessageBoxA, 0, ADDR szDivideBy0, ADDR szException, 0
            mov esp, [esp+08]

            pop dword ptr fs:[0]       ;Remove SEH
            pop ebp

            pop ebp                    ;Restore StackFrame
            pop esp
        ret

    Except2 endp


Cette deuxième fonction crée un troisième SEH qui va gérer les exception de division par 0. Le StackFrame est sauvé dans la structure ERR du SEH pour pouvoir retourner de la fonction sans problème même en cas d'exception.
Le Handler va tester si c'est bien une exception Divide By Zero, dans le cas contraire il renvoit l'exception au prochain EHandler dans la chaine. Si c'est une telle excption, il l'affiche dans une MessageBoxA et restaure le StackFrame qui se trouve dans la structure ERR du handler. Un simple RET correspondra dès lors à un retour normal de la fonction Except2. On pourrait par exmple mettre -1 dans EAX pour prévenir la fonction Except1 qu'il y a eu une erreur mais en aucun cas, l'exception aura une conséquence sur l'exécution de la fonction Except1

Voilà, c'est enfin fini.. j'espère que je n'ai pas été trop flou sur tel ou tel point. Si vous désirez plus d'expliquations, n'hésitez pas : teejee@hotmail.com.
Amicalement,
TeeJi


SEH.ASM    (c)2000 TeeJi

;==========================================================================================
; SEH programme de démonstration des listes chainées de SEH et utilisation de RtlUnwind
;==========================================================================================
; Le programme Install un SEH qui sera le FinalEH et qui ne fera que quitter le programme.
; Ensuite la fonction Except1 est appelée, et supposons qu'elle utilise une zone mémoire
; à risque, j'ai donc installé un SEH qui gère ce type d'eception. Cette fonction Except1
; fait appel à la fonction Except2, et supposons que celle-ci fasse des opérations mathémaique,
; j'ai donc installé un SEH qui gère les exceptions Divide By Zero.
;
; Si une Exception Divide By Zero survient dans la fonction Except2, le SEH de Except2 va
; restauré le StackFrame et retourner comme si de rien était.
;
; Si une Exception Acces Violation survient, alors le SEH de Except2 va passer la main à
; l'SEH de Except1 qui va utiliser Unwind pour restaurer le StackFrame et enlever le SEH
; de l'Except2 et de l'Except1, ensuite remettre un SafeStackFrame et sauté à un Code Safe.
;
; Rem : RtlUnwind enlève les Structures ERR situées avant FinalERRStruct et exécute chaque
; ---   Handler pointé par les ERR Struct enlevées avec en param ExceptionRecord.ExceptionFlags
;       égale à EH_UNWINDING pour le prévenir de l'Unwind. Mes SEH ne doivent rien faire
;       lorsqu'il y a Unwinding mais vous pourriez par exemple modifier une variable, ou
;       arrêter un Thread, ou créer une nouvelle Window, etc... Libre à vous maintenant
;       de l'utiliser comme bon vous semble !
;==========================================================================================
;                                                                             (c)2000 TeeJi
;==========================================================================================

      .486
      .model flat, stdcall
      option casemap :none   ; case sensitive
      assume fs : nothing

; #########################################################################

      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc

      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib

.data?

     FinalERRStruct     dd ?
     SafeEBP            dd ?
     SafeESP            dd ?
     LastExceptionCode  dd ?

.data

    szException db "Exception occurs !",0
    szAccessViolation db "Access Violation",0
    szDivideBy0 db "Divide by 0",0
    szLastException db "Last Exception Before SafePlace",0
    szNoException db "No Exception",0
 
.const

     NO_EXCEPTION equ -1

.code

start:

    push offset MyFinalEHandler  ;Set SEH
    push fs:[0]
    mov fs:[0], esp

    mov FinalERRStruct, esp      ;Save SafeStackframe
    mov SafeESP, esp
    mov SafeEBP, ebp

    mov LastExceptionCode, NO_EXCEPTION
 
    call Except1                 ;Call Exception Fonction

   SafePlace:

    ;-----------------------------------------------------------------------------------
    ; Ici vous pouvez mettre du code qui s'exécutera normalement sans problème
    ; ou tout au moin indépendemment des exceptions qui seraient parvenues précédemment
    ;------------------
    ; Pour l'exemple, lorsqu'une Exception survient, sont code est sauvée dans
    ; LastExceptionCode si c'est pas lors de l'Unwind. Le SafeCode va tester et afficher
    ; le contenu de LastExceptionCode.
    ;------------------------------------------------------------------------------------

    mov eax, LastExceptionCode
    .if eax==NO_EXCEPTION
     invoke MessageBoxA, 0, ADDR szNoException, ADDR szLastException, 0
    .elseif eax==EXCEPTION_INT_DIVIDE_BY_ZERO
     invoke MessageBoxA, 0, ADDR szDivideBy0, ADDR szLastException, 0
    .elseif eax==EXCEPTION_ACCESS_VIOLATION
     invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szLastException, 0
    .endif
 
    pop dword ptr fs:[0]         ;Remove SEH
    add esp, 4

    invoke ExitProcess, NULL     ;Exit Process
 
    ;------------------------------------------------------------------------
    ; Except1 PROC - Supposons qu'elle utilise une zone mémoire à risque
    ;------------------------------------------------------------------------
    ; Install un SEH qui gère les exception Access Violation
    ; Et appel la fonction Except2
    ;------------------------------------------------------------------------
    Except1 proc

        push offset @@EHandler   ;Set SEH
        push fs:[0]
        mov fs:[0], esp

        call Except2             ;Call next Except Func

        pop dword ptr fs:[0]     ;Remove SEH
        add esp, 4
        ret

        ;----------------------------------------------
        ; Gère uniquement les violations mémoire
        ;----------------------------------------------
        @@EHandler:
            mov eax, [esp+04]
            assume eax:PTR EXCEPTION_RECORD
            test [eax].ExceptionFlags, 2
            jnz @F
            push [eax].ExceptionCode
            pop LastExceptionCode

           @@:
            cmp [eax].ExceptionCode, EXCEPTION_ACCESS_VIOLATION
            jz @F
            mov eax, EXCEPTION_EXECUTE_HANDLER
            ret

           @@:
            invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szException, 0

            ;RtlUnwind va enlever les SEH jusqu'au FinalERRStruct dans la chaine
            ;          et appelera chaque Handler enlevé avec comme paramètre
            ;          ExceptionFlag = EH_UNWINDING

            mov eax, esp
            push NULL              ;Return Value  // unused
            add eax, 0Ch
            push dword ptr[eax]    ;pExceptionRecord
            push offset @F         ;Return Addr
            sub eax, 4
            push FinalERRStruct    ;pErrStruct
            call RtlUnwind         ;Unwind

           @@:

            ; Ici, je propose 2 manière de se retrouver à la SafePlace...
            ; soit par la petite porte en restaurant le StackFrame
            ; soit par la voie normale en modifiant le Context
            ;
            ; Rem : Une autre possibilitée aurait été de laisser le SafePlace
            ; ---   Restaurer un SafeStackFrame et de mettre l'offset SafePlace
            ;       comme address de retour de RtlUnwind.

;           jmp @@LittleDoor
            jmp @@NormalWay
 

           @@LittleDoor:
            mov ebp, SafeEBP       ;Set SafeStackframe
            mov esp, SafeESP
            push SafePlace         ;Jmp to SafePlace
            ret

           @@NormalWay:
            mov eax, [esp+0Ch]
            assume eax:PTR CONTEXT
            push SafeEBP            ;Restaure un SafeStackFrame
            pop [eax].regEbp
            push SafeESP
            pop [eax].regEsp
            push offset SafePlace   ;Modifie l'EIP
            pop [eax].regEip

            mov eax, EXCEPTION_CONTINUE_SEARCH    ;On a réparé l'erreur... essayons de rééxécuter le Thread.
            ret
 
    Except1 endp

    ;------------------------------------------------------------------------
    ; Except2 PROC - Supposons qu'elle utilise des divisions d'entier ( DIV )
    ;------------------------------------------------------------------------
    ; Install un SEH qui gère les exception Divide By Zero
    ; Et créé soit une Exception Divide By Zero
    ;         soit une Exception Access Violation
    ;------------------------------------------------------------------------
    Except2 proc

        push esp                   ;SetSEH
        push ebp
        push offset @@EHandler
        push fs:[0]
        mov fs:[0], esp

        ;---------------------------------------------
        ; Mettez un ';' devant l'exception que vous ne
        ; voulez pas créer.
        ;---------------------------------------------
 
        xor eax, eax
       div ax                     ;Raise a Divide by Zero Exception
;        mov dword ptr[eax], eax    ;Raise a Access Violation Exception

        pop dword ptr fs:[0]       ;Remove SEH

        add esp, 0Ch
        ret

        ;----------------------------------------------
       ; Gère uniquement les divisions par 0
      ;----------------------------------------------
        @@EHandler:
            mov eax, [esp+4]
            assume eax:ptr EXCEPTION_RECORD
            test [eax].ExceptionFlags, 2
            jnz @F
            push [eax].ExceptionCode
            pop LastExceptionCode

           @@:
            cmp [eax].ExceptionCode, EXCEPTION_INT_DIVIDE_BY_ZERO
            jz @F
            mov eax, EXCEPTION_EXECUTE_HANDLER
            ret

           @@:
            invoke MessageBoxA, 0, ADDR szDivideBy0, ADDR szException, 0
            mov esp, [esp+08]

            pop dword ptr fs:[0]   ;Remove SEH
            pop ebp

            pop ebp                ;Restore StackFrame
            pop esp
        ret

    Except2 endp

    ;----------------------------------------------
    ; Si l'exception n'a pas été gèrée, ExitProcess
    ;----------------------------------------------
    MyFinalEHandler:
        invoke ExitProcess, 0
 
end start

J'espère que ce petit tour d'horizon des anti XXX de ce programme vous aura convaincu qu'il y a toujours et beaucoup à apprendre...

Il reste maintenant à cracker le programme, mais c'est une autre histoire !

Bonne Journée

Christal