- Есть ссылка на сайт malware.zn.school-ctf.org. Видим там модуль чата, XSS в тексте сообщения.
- В админке есть несколько чатов, первый из которых датирован сильно раньше начала таски.
-
В этом чате есть ссылка на архив и хост:
- http://sibears.ru/files/drop.zip
- vdooli.zn.school-ctf.org
-
В архиве 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
.