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 approté ce complément indispensable à la compréhension de la façon dont l'exception fonctionne:






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