Introduction à l’ARM par le reverse d’un malware

Introduction à l’ARM par le reverse d’un malware

On change un peu, just for fun !

Cela fait plusieurs articles que nous jouons avec le noyau Windows, c’est sympa mais parfois il faut savoir changer, sans quoi la routine s’installe. Donc cette fois on va faire une petite introduction au reversing sur ARM au lieu d’Intel. Et pour se faire quoi de mieux que de reverser un malware ou tout du moins ses protections d’anti-reversing ? C’est donc ce que l’on va faire, just for fun 😉

Petits points sur l’ARM

L’ARM est un type de processeur, il possède donc son propre jeu d’instructions, mais n’ayez crainte on s’y habitue très vite. Il faut dans un premier temps savoir que les instructions sont alignées sur 4 octets, il est donc plus difficile de faire du junk code comme sur Intel bien que ce soit possible. Les registres ne sont pas nommés EAX, ECX etc. mais R0, R1, R2, etc. Le pointeur sur la prochaine d’instruction à exécuter est nommé PC, les « call » sont des « bl » et les « jmp » des « b ». Important également on n’a pas directement nommé l’instruction « xor » mais « eor » à la place, ce qui est plus logique par rapport au « exclusive or ».
Il y a toujours une pile, cette fois on empile pas EIP mais un EIP précédent, je m’explique. Quand on fait un BL on place le PC dans le registre LR qui est notre sauvegarde de EIP et lui on l’empilera. Autrement dit les « buffer overflow » sont toujours d’actualité.

Voilà pour les grandes lignes, la pratique sera surement plus claire.
On pourrait reverser « encore » un malware android à manger du java et tout ce qui va avec, mais on va changer un peu et se faire un malware Windows Phone ! Comme ça en plus on a du code compilé et ça c’est plus fun.

Windows Phone

Sous Windows Phone on a un « Windows CE » comme système d’exploitation, il est fait pour être minimaliste et donc consommer un minimum d’espace de stockage et mémoire. Son architecture logicielle est relativement proche d’un Windows classique et on exécute toujours des EXE au format PE comme ça on ne sera pas perdu.

Reversing du malware

Nous avons pris un malware au hasard dans une liste mais qui va se révéler intéressant à étudier. Il s’agit de « PMCryptic ». Sur Internet on trouve pas mal d’analyses par sandboxes mais aucune étude un tant soit peu détaillée. Nous allons donc au moins montrer les protections anti-reversing qu’il a mis en place.
On charge notre .EXE dans IDA et on regarde un peu la tête que ça a :

.text:00012480 start ; DATA XREF: .pdata:00015000o
.text:00012480
.text:00012480 var_20 = -0x20
.text:00012480
.text:00012480 MOV R12, SP
.text:00012484 STMFD SP!, {R4-R7,R11,R12,LR}
.text:00012488 ADD R11, SP, #0x1C
.text:0001248C SUB SP, SP, #4
.text:00012490 MOV R4, R3
.text:00012494 MOV R5, R2
.text:00012498 MOV R6, R1
.text:0001249C MOV R7, R0
.text:000124A0 BL sub_124F4
.text:000124A4 MOV R3, R4
.text:000124A8 MOV R2, R5
.text:000124AC MOV R1, R6
.text:000124B0 MOV R0, R7
.text:000124B4 BL sub_11000
.text:000124B8 MOV R4, R0
.text:000124BC STR R4, [R11,#var_20]
.text:000124C0 B loc_124CC
.text:000124C4 ; ---------------------------------------------------------------------------
.text:000124C4 MOV R4, R0
.text:000124C8 BL sub_12664
.text:000124CC
.text:000124CC loc_124CC ; CODE XREF: start+40j
.text:000124CC MOV R0, R4
.text:000124D0 BL sub_12664
.text:000124D4 LDMDB R11, {R4-R7,R11,SP,LR}
.text:000124D8 BX LR
.text:000124D8 ; End of function start

Une fonction de chargement sans rien de particulier et un appel (offset 000124B4) à sub_11000, sur les compilateurs Visual Studio le « main » est souvent mis au début du programme. C’est donc ici que nous allons continuer. On arrive sur une fonction constituée de quatre blocs.
Dans le premier on a la chose suivante :

STMFD SP!, {LR}
MOVCS R3, #0x97
MOVLT R1, #0xB1
MOVCS R0, #0xAC
MOVLE R0, #0xBC
MOVLTS R0, #0x90
MOVEQ R1, #0xD7
MOVVSS R1, #0x27
MOVCC R0, #0xF7
MOVPLS R1, #0xD7
MOVLSS R2, #0x58
[…]
CMP R4, R3
AND R0, R3, #8
TEQ R3, #6
MOV R0, R3,LSL#2
ADD R0, R1, R0
B loc_110C0

Mmmmm, mais c’est pas beau du tout ! On a un bel exemple d’ « obfuscation » de code bien moche et qui ne sert strictement à rien à cause du « B » qui nous indique un saut vers un autre code. Puis nous avons :

loc_110C0
MOVLTS R0, #0x2B
MOVNES R1, #0xA8
MOVLSS R0, #0x83
MOVMIS R3, #0x58
MOVCSS R2, #0x70
MOVEQ R3, #0x4D
MOVEQS R0, #0x7A
MOVCC R1, #0xDD
[…]
MOV R1, #0xFD
ORR R0, R2, R3
AND R0, R3, #8
CMP R4, R3
B loc_11154

Contre toute attente ils nous ont remis un bloc aussi moche et aussi inutile. Ne nous attardons pas sur les instructions vides et passons au troisième bloc qui lui est intéressant !

loc_11154
MOV R3, #8
ADRL R2, dword_11144
ADRL R1, loc_12420
ADRL R0, loc_11190
SUB R1, R1, R0
ADRL R0, loc_11190
BL sub_11088
ADRL R0, loc_11190
MOV PC, R0

« ADRL » est en quelque sorte un « MOV » d’une adresse dans un registre. On voit donc clairement une initialisation d’arguments avant un appel à « sub_11088 ». La fin du bloc est aussi à noter, plutôt que d’effectuer un « B » sur une adresse, place une adresse dans PC (qui est notre pointeur d’instructions), on fait donc pareil qu’un saut mais autrement. Pour comprendre le pourquoi il faut regarder les lignes suivantes :

.text:00011180 ADRL R0, loc_11190
.text:00011188 MOV PC, R0
.text:00011188 ; END OF FUNCTION CHUNK FOR sub_11000
.text:0001118C ; ---------------------------------------------------------------------------
.text:0001118C MOV R0, #1
.text:00011190 ; START OF FUNCTION CHUNK FOR sub_11000
.text:00011190
.text:00011190 loc_11190 ; CODE XREF: sub_11000+188j
.text:00011190 ; DATA XREF: sub_11000+168o ...
.text:00011190 STMCCFD SP, {R1,R3,R4,R6-R8,R10-SP,PC}
.text:00011194 SVCCS 0xE591D9
.text:00011198 STRT R2, [LR],#0x97E
.text:0001119C LDCVC p14, c4, [R11,#0x228]
.text:000111A0 MOVLE R11, #0xDDD9
.text:000111A4 STRCCB R6, [R9],#-0x6D7
.text:000111A8 BCC 0x850654

Au final on ne saute qu’une instruction « MOV     R0, #1 ». Il est donc fort probable que ce soit un système anti-émulation et que cette variable fasse quitter le programme si elle est à 1. Une autre chose intéressante est la suite du code à partir de loc_11190, elle n’est pas vraiment claire et semble même ne pas être exécutable de façon fiable, il y a donc de fortes chances que nous soyons en présence d’un espace chiffré. La dernière fonction exécutée est sub_11088, regardons de plus près cette fonction.

OOOoooo, la jolie boucle avec un « EOR », comprendre XOR au milieu. « STRB » écrit 1 octet à une adresse, ici à R0 + R4. Étant donné que R4 est initialisé à 0 au début nous pouvons en déduire que R0 contient d’adresse de destination. R0 est initialisé avec l’adresse loc_11190 et qui est notre code incompréhensible. En remontant un peu dans le code on sait que R3 est initialisé à 8 et R2 pointe sur une zone mémoire avec quelques données brutes. On peut donc réécrire la routine comme suit :

char key[]={ 0xD9, 0xBD, 0xD, 0xD3, 0xD9, 0x66, 0x49, 0xDE };
int i, y=0;

for (i=0x590; i<0x1820; i++){
ExeFile[i] ^= key[y%sizeof(key)];
y++;
}

On déchiffre donc la suite du code et on continue. Pour la suite nous commenterons un peu plus le code assembleur.
Une fois le déchiffrement passé nous avons :

.text:00011170 SUB R1, R1, R0
.text:00011174 ADRL R0, CryptedCode
.text:0001117C BL Crypt ; R0 = Address of code to decrypt
.text:00011180 ADRL R0, CryptedCode
.text:00011188 MOV PC, R0
.text:00011188 ; END OF FUNCTION CHUNK FOR sub_11000
.text:0001118C ; ---------------------------------------------------------------------------
.text:0001118C MOV R0, #1
.text:00011190 ; START OF FUNCTION CHUNK FOR sub_11000
.text:00011190
.text:00011190 CryptedCode ; CODE XREF: sub_11000+188j
.text:00011190 ; DATA XREF: sub_11000+168o ...
.text:00011190 B loc_111A4
.text:00011190 ; END OF FUNCTION CHUNK FOR sub_11000
.text:00011190 ; ---------------------------------------------------------------------------
.text:00011194 CypherKey2 DCD 0xF1ACF700, 0x37A394A7, 0xA3D22853, 0
.text:00011194 ; DATA XREF: sub_11000+398o
.text:000111A4 ; ---------------------------------------------------------------------------
.text:000111A4 ; START OF FUNCTION CHUNK FOR sub_11000
.text:000111A4
.text:000111A4 loc_111A4 ; CODE XREF: sub_11000:CryptedCodej
.text:000111A4 B loc_111E4
.text:000111A4 ; END OF FUNCTION CHUNK FOR sub_11000
.text:000111A8
.text:000111A8 ; =============== S U B R O U T I N E =======================================
.text:000111A8
.text:000111A8
.text:000111A8 sub_111A8 ; DATA XREF: .text:00011354o
.text:000111A8 STMFD SP!, {R4-R7,LR}
.text:000111AC MOV R4, #0

C’est donc bien la suite du code qui devait être déchiffrée. Nous voyons plusieurs sauts à la suite, il peut y avoir deux explications, soit du « slicing », c’est-à-dire des sauts en aléatoires dans le code pour empêcher une lecture fluide et aux débuggeurs / désassembleurs de se repérer. Soit pour stocker des données qui devaient êtres chiffrées pour ne pas être lus en clair. Nous opterons pour la deuxième solution, bien qu’il y ait pas mal de sauts quand même :

 

En suivant les sauts nous arrivons à un autre bloc relativement similaire à notre premier.

loc_11394
MOV R3, #8
ADRL R2, CypherKey2
ADRL R1, EndCryptedCode
ADRL R0, CryptedCode2
SUB R1, R1, R0 ; initialize size of code to decrypt
ADRL R0, CryptedCode2
BL ChoiceCrypt
ADR PC, CryptedCode2
ADR PC, CryptedCode2
MOV R0, #2

Allons dans le vif du sujet et regardons la fonction que nous avons (en off) joliment nommé « ChoiceCrypt » :

Dans les quelques premières lignes nous avons un choix de pointeur initialisé par le début de notre clé. On saute alors à cette adresse pour exécuter une fonction, ici de déchiffrement. Il est fort probable que cette méthode ait été mise en place pour perturber des analyses de code automatisées. Dans notre cas la fonction pointée sera la numéro 0 :

Nous allons donc appeler la même fonction de chiffrement que précédemment. Une petite subtilité tout de même, la clé utilisée se trouve juste après l’identifiant de fonction, nous avons la fonction de déchiffrement suivante :

char key[]={ 0xF7, 0xAC, 0xF1, 0xA7, 0x94, 0xA3, 0x37, 0x53 };
int i,y=0;

for (i=0x7cc; i<0x1820; i++){
ExeFile[i] ^= key[y%sizeof(key)];
y++;
}

Un deuxième niveau est maintenant déchiffré, encore une fois nous avons du code invalide et nous pouvons donc nous attendre à une autre boucle de déchiffrement. Cette fois l’index de la fonction à utiliser est différent :

Nous allons donc exécuter la troisième fonction du tableau de pointeurs.

.text:000111E8 STMFD SP!, {R4-R7,LR}
.text:000111EC MOV R4, #0
.text:000111F0 MOV R5, #0
.text:000111F4
.text:000111F4 loc_111F4 ; CODE XREF: Crypt_AND_0xFE+34j
.text:000111F4 LDRB R6, [R0,R4]
.text:000111F8 LDRB R7, [R2,R5]
.text:000111FC AND R7, R7, #0xFE
.text:00011200 EOR R6, R6, R7
.text:00011204 STRB R6, [R0,R4]
.text:00011208 ADD R4, R4, #1
.text:0001120C ADD R5, R5, #1
.text:00011210 TEQ R5, R3
.text:00011214 MOVEQ R5, #0
.text:00011218 CMP R4, R1
.text:0001121C BLT loc_111F4
.text:00011220 LDMFD SP!, {R4-R7,PC}

Très similaire à la précédente fonction de déchiffrement au détail près que nous avons un « AND » entre temps. La fonction de déchiffrement est alors des plus triviale :

char key[]={ 0x6A, 0xC8, 0xA6, 0x96, 0x30, 0x41, 0xCD, 0x1B };
int i,y=0;

for (i=0xad0; i<0x1820; i++){
ExeFile[i] ^= (key[y%sizeof(key)] & 0xFE);
y++;
}

Et ça y est le malware est totalement déchiffré nous pouvons l’étudier comme nous le souhaitons. Il faut préciser que toutes les opérations ont été réalisées en statique et que le résultat final ne peut pas être directement exécuté (sinon il se re-chiffrerait).

Conclusion

Bon on n’a pas été très gentil avec ce malware quand même, ses fonctions de chiffrement sont très simplistes mais elles sont justifiées ! La beauté de ce malware mobile est qu’il se re-chiffre différemment à chaque exécution. Pour la suite le malware ne fait rien d’intéressant à part se dupliquer mettre un autorun etc. etc. Pour tout ça des analyses en sandboxs ont déjà été réalisés et sont suffisamment précises.

Une référence intéressante : http://www.symantec.com/connect/blogs/smart-worm-smartphone-wincepmcryptica