# **Write-up Secure Database (CTFZone 2021)**
![](https://i.imgur.com/qorHxoo.jpg)
> Самый секьюрный канал на бешаном востоке [@ch4nnel1](https://t.me/ch4nnel1)
Original write-up: [https://hackmd.io/UKS6HgoKTj-LNbw-ajGGVg](https://hackmd.io/UKS6HgoKTj-LNbw-ajGGVg)
## Разбор![](https://i.imgur.com/4sj6Tl8.png)
Сам таск (https://ctf.bi.zone/challenges/8)
Сначала, постучимся на сервер с помощью приведенной команды `nc securedb.2021.ctfz.one 7777`, он попросит у нас id дадим ему id, который столь любезно дали нам админы `229386e9-d6b0-4006-babc-5a15f7acee72`
![](https://i.imgur.com/oSf4CW4.png)
Просто блекбокся сервер, мы мало чего получим, так что стоит взглянуть файл из таска. В нем как нестрано дан бинарник.Так как время поджимало (до конца ctf'а осталось около часа), то я сразу ~~подключил читы~~ воспользовался идой.Там в списке функций сразу приметилась одна `start_routine`, рутина все такое.
![](https://i.imgur.com/Vr02UJY.png)
Открыв ее сразу замечаем строчку `"To retrieve your medical report..."`, что говорит о том, что мы нашли функцию с которой стоит начать курить бинарь.### start_routine```clike=1void __fastcall __noreturn start_routine(void *a1){  __int16 v1; // [rsp+20h] [rbp-20h] BYREF  unsigned __int16 v2; // [rsp+22h] [rbp-1Eh]  int fd; // [rsp+24h] [rbp-1Ch]  void *buf; // [rsp+28h] [rbp-18h]  void *ptr; // [rsp+30h] [rbp-10h]  unsigned __int64 v6; // [rsp+38h] [rbp-8h]
  v6 = __readfsqword(0x28u);  fd = *(_DWORD *)a1;  buf = "To retrieve your medical report, please enter the unique identifier.\n\n";  ptr = 0LL;  v2 = 70;  v1 = 0;  if ( fd )  {    if ( send(fd, buf, v2, 0) != -1 )    {      ptr = (void *)sub_4013C8((unsigned int)fd, &v1;;      if ( ptr )      {        sub_4018E5((unsigned int)fd, ptr, (unsigned int)v1);        free(ptr);      }    }    shutdown(fd, 2);    close(fd);    puts("Thread exit..");    pthread_exit(0LL);  }  puts("Connection failed, thread exit..");  pthread_exit(0LL);}```Дальше нас интересует функция `sub_4013C8`, в которую передается сокет (20 строчка).### sub_4013C8```clike=1char *__fastcall sub_4013C8(int a1, __int16 *a2){  char *result; // rax  __int16 v3; // [rsp+10h] [rbp-C0h]  __int16 v4; // [rsp+12h] [rbp-BEh]  int v5; // [rsp+14h] [rbp-BCh]  int v6; // [rsp+14h] [rbp-BCh]  int v7; // [rsp+14h] [rbp-BCh]  void *s; // [rsp+20h] [rbp-B0h]  char *ptr; // [rsp+28h] [rbp-A8h]  struct timeval timeout; // [rsp+30h] [rbp-A0h] BYREF  fd_set readfds; // [rsp+40h] [rbp-90h] BYREF  unsigned __int64 v12; // [rsp+C8h] [rbp-8h]
  v12 = __readfsqword(0x28u);  memset(&readfds, 0, sizeof(readfds));  readfds.fds_bits[a1 / 64] |= 1LL << (a1 % 64);  timeout.tv_sec = 180LL;  timeout.tv_usec = 0LL;  v5 = select(a1 + 1, &readfds, 0LL, 0LL, &timeout);  puts("Receiving data..");  if ( v5 == -1 )    goto LABEL_2;  if ( !v5 )    goto LABEL_28;  puts("Data is available now..");  s = malloc(3uLL);  if ( !s )    return 0LL;  memset(s, 0, 3uLL);  if ( recv(a1, s, 2uLL, 0) == -1 )  {    free(s);    return 0LL;  }  *a2 = (unsigned __int8)sub_401847(s);  free(s);  if ( !*a2 )    return 0LL;  if ( *a2 <= 0 || *a2 > 36 )    return 0LL;  v6 = select(a1 + 1, &readfds, 0LL, 0LL, &timeout);  if ( v6 == -1 )  {LABEL_2:    puts("Select error...");    return 0LL;  }  if ( v6 )  {    puts("Data is available now..");    ptr = (char *)malloc(*a2 + 1);    if ( ptr )    {      memset(ptr, 0, *a2 + 1);      v3 = 0;      timeout.tv_sec = 1LL;      while ( *a2 > v3 )      {        v7 = select(a1 + 1, &readfds, 0LL, 0LL, &timeout);        if ( v7 == -1 )        {          puts("Select error...");          free(ptr);          return 0LL;        }        if ( !v7 )          break;        v4 = recv(a1, &ptr[v3], *a2 - v3, 0);        if ( v4 <= 0 )        {          free(ptr);          return 0LL;        }        v3 += v4;      }      if ( *a2 == v3 )      {        result = ptr;      }      else      {        puts("Invalid data...");        free(ptr);        result = 0LL;      }    }    else    {      puts("ERROR..");      result = 0LL;    }  }  else  {LABEL_28:    puts("No data within 3 minutes. Exiting..");    result = 0LL;  }  return result;}```Тут нас будет интересовать получение данных и что потом с ними происходит. Для начала это строчки 31 и 36, по строчке 31 все понятно в переменную `s` записываются 2 первых байта, и после если все хорошо в 36 строчке `s` передается в функцию `sub_401847`. ### sub_401847```clike=1__int64 __fastcall sub_401847(unsigned __int8 *a1){  unsigned __int8 *v1; // rax  unsigned __int8 v4; // [rsp+1Dh] [rbp-3h]  unsigned __int8 v5; // [rsp+1Eh] [rbp-2h]  unsigned __int8 v6; // [rsp+1Fh] [rbp-1h]  char v7; // [rsp+1Fh] [rbp-1h]
  v4 = 0;  v5 = 0;  while ( v4 <= 1u )  {    v1 = a1++;    v6 = *v1;    if ( *v1 <= '/' || v6 > '9' )    {      if ( v6 <= '`' || v6 > 'f' )      {        if ( v6 <= '@' || v6 > 'F' )        {          puts("ERROR: hex convert to int..");          return 0LL;        }        v7 = v6 - 55;      }      else      {        v7 = v6 - 87;      }    }    else    {      v7 = v6 - 48;    }    v5 = (16 * v5) | v7 & 0xF;    ++v4;  }  return v5;}```Чуть изучив эту функцию легко понять, что она конвертит хексовое представление байта в инт (`int(s[:2], 16)` *- псевдокод в студию*).
Окей, вернемся к анализу [sub_4013C8](#sub_4013C8). Этот инт как раз записывается по адресу `a2`, из дальнейшего кода модно понять, что в `a2` хранится длина  данных, которые будут при приняты дальше (после первых 2 символов), записаны в `ptr` и возвращены в [start_routine](#start_routine). В результате в `start_routine` `ptr` хранит переданные данные (без первых двух байт), а `v1` - размер этих данных, и все это вместе с сокетом передается в `sub_4018E5`.### sub_4018E5```clike==1__int64 __fastcall sub_4018E5(int a1, __int64 a2, unsigned int a3){  const char *v3; // rax  __int64 result; // rax  _BYTE n_4[12]; // [rsp+24h] [rbp-2Ch] BYREF  __int64 src; // [rsp+30h] [rbp-20h] BYREF  const char *v8; // [rsp+38h] [rbp-18h] BYREF  void *s; // [rsp+40h] [rbp-10h]  unsigned __int64 v10; // [rsp+48h] [rbp-8h]
  v10 = __readfsqword(0x28u);  *(_QWORD *)&n_4[4] = 0LL;  src = 0LL;  v8 = 0LL;  s = 0LL;  *(_QWORD *)n_4 = (unsigned int)sqlite3_open("patient_data.db", &n_4[4]);  if ( *(_DWORD *)n_4 )  {    v3 = (const char *)sqlite3_errmsg(*(_QWORD *)&n_4[4]);    fprintf(stderr, "Cannot open database: %s\n", v3);    sqlite3_close(*(_QWORD *)&n_4[4]);    result = 0LL;  }  else  {    s = malloc(0x34uLL);    if ( s )    {      memset(s, 0, 0x34uLL);      memcpy(s, "SELECT * FROM PatientList WHERE HashID = '", 0x2AuLL);      src = sub_401B1B(a2, a3, 64400LL);      memcpy((char *)s + 42, &src, 8uLL);      memcpy((char *)s + 50, "'", sizeof(char));      if ( (unsigned int)sqlite3_exec(*(_QWORD *)&n_4[4], s, sub_401CE1, a1, &v8) )      {        fprintf(stderr, "SQL error: %s\n", v8);        free(s);        sqlite3_free(v8);        sqlite3_close(*(_QWORD *)&n_4[4]);        result = 0LL;      }      else      {        free(s);        sqlite3_close(*(_QWORD *)&n_4[4]);        result = 1LL;      }    }    else    {      puts("ERROR..");      sqlite3_close(*(_QWORD *)&n_4[4]);      result = 0LL;    }  }  return result;}```Одно из самых интересных мест для нас это строчки 30-36 в них создается строчка `s="SELECT * FROM PatientList WHERE HashID = '{src}'"`, где `src` занимает 8 байт и получается посредством передачи в `sub_401B1B` значений `ptr` и `v1` из `sub_4018E5`. После как нестрано `s` используется как sql запрос, форматированный результат которого и выдается нам по сокету назад.
Дальше я пытался понять каким HashID обладает нужный мне пациент, но решил подумать об этом позже, когда научусь генерить нужные `s`. Так что я пошел изучать `sub_401B1B`.### sub_401B1B```clike=1unsigned __int64 __fastcall sub_401B1B(unsigned __int8 *a1, unsigned __int64 a2, __int64 a3){  unsigned __int8 *v3; // rax  unsigned __int64 v5; // [rsp+20h] [rbp-38h]  unsigned __int8 *i; // [rsp+28h] [rbp-30h]
  v5 = a3 ^ (0xC6A4A7935BD1E995LL * a2);  for ( i = a1; i != &a1[8 * (a2 >> 3)]; i += 8 )  {    v3 = i;    v5 = 0xC6A4A7935BD1E995LL       * ((0xC6A4A7935BD1E995LL         * (((0xC6A4A7935BD1E995LL * *(_QWORD *)v3) >> 47) ^ (0xC6A4A7935BD1E995LL * *(_QWORD *)v3))) ^ v5);  }  switch ( a2 & 7 )  {    case 1uLL:      goto LABEL_11;    case 2uLL:      goto LABEL_10;    case 3uLL:      goto LABEL_9;    case 4uLL:      goto LABEL_8;    case 5uLL:      goto LABEL_7;    case 6uLL:      goto LABEL_6;    case 7uLL:      v5 ^= (unsigned __int64)i[6] << 48;LABEL_6:      v5 ^= (unsigned __int64)i[5] << 40;LABEL_7:      v5 ^= (unsigned __int64)i[4] << 32;LABEL_8:      v5 ^= (unsigned __int64)i[3] << 24;LABEL_9:      v5 ^= (unsigned __int64)i[2] << 16;LABEL_10:      v5 ^= (unsigned __int64)i[1] << 8;LABEL_11:      v5 = 0xC6A4A7935BD1E995LL * (*i ^ v5);      break;    default:      return ((0xC6A4A7935BD1E995LL * ((v5 >> 47) ^ v5)) >> 47) ^ (0xC6A4A7935BD1E995LL * ((v5 >> 47) ^ v5));  }  return ((0xC6A4A7935BD1E995LL * ((v5 >> 47) ^ v5)) >> 47) ^ (0xC6A4A7935BD1E995LL * ((v5 >> 47) ^ v5));}```Увидев эту страшную крипто-функцию, я сразу же пошел гуглить константу `C6A4A7935BD1E995`, первой же ссылкой получил https://github.com/vitkyrka/kninja/blob/master/mmh2.py, чуть изучив этот код я пришел к выводу, что он очень схож с функцией `sub_401B1B`. Подав им на входы одинаковые данные (`sub_401B1B` и коду), я получил одинаковый результат, что лучше всего доказывает, что `sub_401B1B` есть MurmurHash2 (такое название было в ссылке выше).
Загуглив `MurmurHash2 reverse`, натыкаемся на любимый [сайт, stackoverflow](https://stackoverflow.com/questions/59976917/is-it-possible-to-reverse-a-murmurhash-in-python-with-mmh3). Хоть конкретно в этом вопросе и спрашивают про mmh3, а не про mmh2 во втором ответе есть полезная [ссылочка с инверсией разных алгоритмов](http://bitsquid.blogspot.com/2011/08/code-snippet-murmur-hash-inverse-pre.html). Воспользовавшись все тойже константой `C6A4A7935BD1E995`, найдем нужную функцию инверсии `murmur_hash_64_inverse`.
Чуть поигравшись, понастраивая и помучавшись с ендингами, получил [такой сишный скрипт](https://github.com/osogi/writeups/blob/main/ctfzone2021/Secure_db/inverse_murmurr.c), который получает в аргументах желаемую 8 битную строчку, и выдает 8 байт необходимых для генерации такой строчки. Потестил, работает, теперь пришло время вспомнить про `s="SELECT * FROM PatientList WHERE HashID = '{src}'"`, и тут я приисполнился в сознании, осознал (вспомнил), что sql это максимально дырявая технология. Но так как из меня вебер (ну или кто обычно эксплуатирует sql), как из рыбы топор (лучшей аналогии в 4 часа ночи я не придумал), то я попросил помощи у ~~пошлых одуванчиков~~ моей команды
![](https://i.imgur.com/R3BsPum.png)
Они мне предожили использовать `'or not'`, запихнем это в уже скомпилированный [код, что был разработан раньше](https://github.com/osogi/writeups/blob/main/ctfzone2021/Secure_db/inverse_murmurr.c)
![](https://i.imgur.com/FnNHYkT.png)
С первого раза не получилось, но потом я исправился. Теперь все что осталось это отослать эти байтики на сервер, не забыв про 'хедер', который будет равен `08` (так как байтов после него 8 штук). И так же стоит не забыть про греп, чтобы потом не искать флаг в куче мусора.
![](https://i.imgur.com/nL53rvv.png)
Ееее флаг!
## Решение 
1.  Найти набор функций отвечающий за обработку внесенных данных2. Отреверсить их, понять, что:      - из первых двух байт формируется хедер, отвечающий за кол-во последующих байт     - из оставшихся байт посредством функции sub_401B1B, формируется `src`    - приложение делает запрос к бд, вида `"SELECT * FROM PatientList WHERE HashID = '{src}'"`3. Понять, что `sub_401B1B` == MurmurHash24. Найти алгоритм инверсии для MurmurHash25. Найти значение `src` для эксплуатации `"SELECT * FROM PatientList WHERE HashID = '{src}'"` (один из вариантов `src`==`'or not'`)6. С помощью шага 4 сгенерировать байты, которые преобразуются в нужное значение `src` ([пример скрипта](https://github.com/osogi/writeups/blob/main/ctfzone2021/Secure_db/inverse_murmurr.c))7. Отправить нужные байты + хедер на сервер, получить флаг
## Эпилог
Он будет коротким, иметь умных сокомандников - круто! А вообще, вот такие смешаные таски они и вправду прикольные, понимаешь надобность команды.
А может и неочень коротким, вот написал я уже второй райтап на русском для ctf'а, где вроде участвует немало другого народа, незнающих русский язык. Тип вот будет им интересно решение таска, а у меня мало того, что используется свободный стиль (пытаюсь не писать сухо, оставлять коментарии), так и в русском языке есть ошибки.
In short, exuse me if you read my writeups by translater.
![](https://i.imgur.com/amfOYVJ.png)
Using russian text on photo for apology, genius!