23/07/2011

MBRLocker

Introduction

En ce moment je m'amuse à reverse quelques vx. Mais aujourd'hui c'est jour de fête, je suis tombé sur mon premier MBRLocker :

mbr_code

Reverse de la bête

Le problème que j'avais c'était comment dump mon mbr alors que j'étais infécté, revenir au snapshot précédent, attendre que le malware écrive le nouveau mbr et en 1 ligne de python dump le nouveau mbr :

file("vuln_mbr", "wb").write(file(r"\\.\PhysicalDrive0","rb").read(0x200))

Mais j'ai préféré opté pour une autre solution, convertir le disque de ma vm et allé récupérer le mbr à la main :

> vboxmanage clonehd --variant static analyse.vdi temp.vdi
> head -c 348 temp.vdi | hexdump -C
00000000  3c 3c 3c 20 4f 72 61 63  6c 65 20 56 4d 20 56 69  |<<< Oracle VM Vi|
00000010  72 74 75 61 6c 42 6f 78  20 44 69 73 6b 20 49 6d  |rtualBox Disk Im|
00000020  61 67 65 20 3e 3e 3e 0a  00 00 00 00 00 00 00 00  |age >>>.........|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  7f 10 da be 01 00 01 00  90 01 00 00 02 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000150  00 00 00 00 00 10 00 00  00 b0 00 00              |............|
0000015c

On récupère l'offset de notre disque dans le fichier : 0x0000b000.
On go chercher le mbr :

> echo $((0x0000b000))
45056
dd if=./temp.vdi of=./vuln_mbr bs=512 count=1 skip=$((45056/512))

On regarde que c'est bien ca :

> file vuln_mbr
mbr_vuln.bin: x86 boot sector; partition 1: ID=0x7, active, starthead 1, startsector 63, 20948697 sectors, code offset 0x80

Bon bah reste plus qu'à reverse le truc, moins de 512 octets de code ca devrait allé vite :

> objdump -D -b binary -mi386 -Maddr16,data16 mbr_vuln

Je vais pas tout vous balancer tout le disas mais juste des lignes inéressantes :

88:	b4 00                  mov    ah,0x0
8a:	57                     push   di
8b:	cd 16                  int    0x16
8d:	5f                     pop    di
8e:	eb 37                  jmp    0xc7					     

...

c7:	3c 0d                  cmp    al,0xd
c9:	75 55                  jne    0x120
cb: be 8c 7d               mov    si,0x7d8c
ce: bf 00 6c               mov    di,0x6c00
d1: b5 00                  mov    ch,0x0
d3:	ea 72 7d 00 00         jmp    0x0:0x7d72

Ici on fait appel à l'interrupt 0x16 avec ah = 0 afin de lire un caractère, si ce caractère est égale à 0xd (Carriage Return) alors on part en 0x172 ( 0x7d72 - 0x7C00 ), le bootloader est chargé en 0x7C00 par le BIOS.
Surement la routine de vérifiction de notre code :

172: 8a 0e 8b 7d          	  mov    cl,BYTE PTR ds:0x7d8b
176: ac                   	  lods   al,BYTE PTR ds:[si]
177: 8a 15                	  mov    dl,BYTE PTR [di]
179: 80 f2 ed             	  xor    dl,0xed
17c: 47                   	  inc    di
17d: 3a c2                	  cmp    al,dl
17f: 0f 85 58 ff          	  jne    0xdb
183: 49                   	  dec    cx
184: 75 f0                	  jne    0x176
186: ea 36 7d 00 00       	  jmp    0x0:0x7d36

On peut voir clairement que dans %cl, il y aura la taille du code attendu, %si lui pointe vers le serial auquel on va être comparé, %di pointe sur ce qu'on a rentré, donc tous nos carac vont être xored avec 0xED.
Il suffit d'aller voir ce qui traine en 0x18B ( 0x7d8B - 0x7C00 ) :

> hexdump -s 0x18B mbr_vuln
0000018c  07 a0 ad dc d9 d8 bf b9  00 00 00 00 00 00 00 00

>>> chr(0xa0 ^ 0xED)
'M'
>>> chr(0xad ^ 0xED)
'@'
>>> chr(0xdc ^ 0xED)
'1'
>>> chr(0xd9 ^ 0xED)
'4'
>>> chr(0xd8 ^ 0xED)
'5'
>>> chr(0xbf ^ 0xED)
'R'
>>> chr(0xb9 ^ 0xED)
'T'

Le code serait donc : "M@145RT", on teste et ca boot :]

Bonus

Je trouvais leur message, un peu triste, alors j'ai voulu pour le fun coder un petit bootloader qui déssine une bière, mais qui ne fait rien au final, car 512 octets pour pouvoir caller un dession c'est hard, du coup j'ai 2 stage, le stage 1 qui init la gdt, passer en mode protected, on load le printer de la biere en memoire (0x1000), et on finira par l'exec, voici le résultat :

mbr_beer

Le code :