ANTICHAT — форум по информационной безопасности, OSINT и технологиям
ANTICHAT — русскоязычное сообщество по безопасности, OSINT и программированию.
Форум ранее работал на доменах antichat.ru, antichat.com и antichat.club,
и теперь снова доступен на новом адресе —
forum.antichat.xyz.
Форум восстановлен и продолжает развитие: доступны архивные темы, добавляются новые обсуждения и материалы.
⚠️ Старые аккаунты восстановить невозможно — необходимо зарегистрироваться заново.

09.07.2008, 19:51
|
|
Banned
Регистрация: 16.07.2007
Сообщений: 79
Провел на форуме: 801879
Репутация:
337
|
|
Точим exploit Разбор ошибок выдаваемых компилятором gcc при попытке трансляции файла beta.cpp
Исходник - http://milw0rm.com/shellcode/656
Часть исходника:
Код:
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <windows.h>
#define MAX_BUFFER_SIZE 0x1000
#define DEFAULT_PAD_BYTE 0x90
#define MAX_MARKER_SIZE 0x10
#define bool char
#define true 1
#define false 0
char* hex = "0123456789abcdef";
void version(void) {
printf(
"______________________________________________________________________________\n"
"\n"
" ,sSSSis ,sSSSs, Beta v2.0.\n"
" iS\" dP dY\" ,SP Encodes binary data to/from a variety of formats.\n"
" .SP dSS\" ,sS\" Copyright (C) 2003-2005 by Berend-Jan Wever\n"
" dS' Sb ,sY\" <skylined@edup.tudelft.nl>\n"
" .SP dSSP' sSSSSSSP http://spaces.msn.com/members/berendjanwever\n"
"_ iS:_________________________________________________________________________\n"
"\n"
);
return;
}
void help(void) {
printf(
"Beta was developed to convert raw binary shellcode into text that can be\n"
"used in exploit source-code. It can convert raw binary data to a large\n"
"number of encodings.\n"
"\n"
" Usage: BETA [options] [input file name]\n"
"\n"
" input file name Read input from the given file. By default BETA\n"
" reads input from stdin.\n"
"\n"
"General options:\n"
" --help Display this help and exit\n"
" --version Output version information and exit\n"
" --verbose Displays additional information.\n"
" --pause Wait for keypress before exiting.\n"
"\n"
"Encoding options: (default = AA BB CC ...)\n"
" \\x \\xAA\\xBB\\xCC ...\n"
" 0x 0xAA 0xBB 0xCC ...\n"
" %% %%AA%%BB%%CC...\n"
" # oÞ!...\n"
" %%u %%uBBAA%%uDDCC...\n"
" --noencode Don't encode (only do checks).\n"
"\n"
"Layout options: (default = none)\n"
" --chars/line=X Output a new line after every X encoded bytes.\n"
" --quotes Wrap output in quotes. Only usefull in combination\n"
" with chars/line argument.\n"
" --quotesplus Wrap output in quotes and add a '+' at the end\n"
" of each line. Only usefull in combination with\n"
" chars/line argument.\n"
" --spaces Seperate encoding entities by spaces.\n"
" --commas Seperate encoding entities by commas and spaces.\n"
"\n"
"Additional options:\n"
" --padbyte=AA When using a multibyte encoding (e.g. %%uXXXX)\n"
" the data might need some padding. The given byte\n"
" will be used, the default value is %02x.\n"
" --badbytes[=AA[,BB[...]]] Check the input for presence of the given char-\n"
" acters and report where they are found. You can\n"
" supply a comma seperated list of hexadecimal\n"
" character codes and the keywords \"alpha\" and\n"
" \"print\" (to check for the presence of nonalpha-\n"
" numeric or non-printable characters). If no char-\n"
" acters are supplied, the input will be checked for\n"
" the presence of 00, 0A and 0D. \n"
" --marker[=AA[,BB[...]]] The input contains both garbage and data. The data\n"
" is wrapped by the marker bytes, everything before\n"
" the first set and after the last set of marker\n"
" bytes will be ignored. If no marker bytes are\n"
" supplied, \"CC CC CC\" (3xInt3) will be used.\n"
" You can supply up to %d bytes as marker.\n",
DEFAULT_PAD_BYTE, MAX_MARKER_SIZE
);
return;
}
// Find a set of bytes in another set of bytes
char* find_bytes(char* haystack, int haystack_length, char* needle, int needle_length) {
int needle_start = -1, needle_checked = 1;
do {
if (haystack[needle_start+needle_checked] == needle[needle_checked])
// Yes, bytes match, check next byte of needle
needle_checked++;
else {
// No, no match, check next byte of haystack
needle_start++;
needle_checked = 0;
}
if (needle_start + needle_length > haystack_length)
// Not found.
return 0;
} while (needle_checked != needle_length);
// Found!
return haystack + needle_start;
}
int main(int argc, char** argv, char** envp) {
// This will contain the input data
char* buffer;
int buffer_length = 0;
// This will contain the marker
char marker[MAX_MARKER_SIZE];
int marker_length = 0;
// This will keep track of all "bad" bytes
char char_is_bad[0x100];
for (int i = 0; i < sizeof(char_is_bad)/sizeof(*char_is_bad); i++)
char_is_bad[i] = false;
// These will store some values supplied by command line arguments
bool switch_verbose = false, switch_encode = true, switch_pause = false;
char pad_byte = DEFAULT_PAD_BYTE;
int chars_per_line = -1;
char *input_filename = 0;
char *line_header = "", *line_footer = "\n", *footer = "\n";
char *bytes_format = "%02X", *byte_seperator = "";
int bytes = 1;
//--------------------------------------------------------------------------
// Read and handle arguments
for (int argn = 1; argn < argc; argn++) {
//--help ---------------------------------------------------------------
if (stricmp(argv[argn], "--help") == 0) {
version();
help();
if (switch_pause) getchar();
exit(EXIT_SUCCESS);
//--version ------------------------------------------------------------
} else if (stricmp(argv[argn], "--version") == 0) {
version();
if (switch_pause) getchar();
exit(EXIT_SUCCESS);
//--verbose ------------------------------------------------------------
} else if (stricmp(argv[argn], "--verbose") == 0) {
switch_verbose = true;
//--noencode -----------------------------------------------------------
} else if (stricmp(argv[argn], "--noencode") == 0) {
switch_encode = false;
//--noencode -----------------------------------------------------------
} else if (stricmp(argv[argn], "--pause") == 0) {
switch_pause = true;
//--chars/line= --------------------------------------------------------
} else if (strnicmp(argv[argn], "--chars/line=", 13)==0) {
if ((chars_per_line = strtol(&(argv[argn][13]), NULL, 10)) < 1) {
printf("Illegal number of characters per line: \"%s\".\n", &(argv[argn][13]));
if (switch_pause) getchar();
exit(EXIT_FAILURE);
}
//--layout options -----------------------------------------------------
} else if (strcmp(argv[argn], "--quote") == 0 || strcmp(argv[argn], "--quotes") == 0) {
line_header = "\"";
line_footer = "\"\n";
footer = "\"\n";
} else if (strcmp(argv[argn], "--quoteplus") == 0 || strcmp(argv[argn], "--quotesplus") == 0) {
line_header = "\"";
line_footer = "\" +\n";
footer = "\"\n";
} else if (strcmp(argv[argn], "--comma") == 0 || strcmp(argv[argn], "--commas") == 0) {
byte_seperator = ", ";
} else if (strcmp(argv[argn], "--space") == 0 || strcmp(argv[argn], "--spaces") == 0) {
byte_seperator = " ";
//--encoding options ---------------------------------------------------
} else if (stricmp(argv[argn], "\\x")==0) {
bytes_format = "\\x%02X";
} else if (stricmp(argv[argn], "0x")==0) {
bytes_format = "0x%02X";
} else if (stricmp(argv[argn], "#")==0) {
bytes_format = "&#%d;";
} else if (stricmp(argv[argn], "%")==0) {
bytes_format = "%%%02X";
} else if (stricmp(argv[argn], "%u")==0) {
bytes_format = "%%u%04X";
bytes = 2;
//--padbyte ------------------------------------------------------------
} else if (strnicmp(argv[argn], "--padbyte=", 10) == 0) {
char* next_xarg;
pad_byte = strtol(&(argv[argn][10]), &next_xarg, 0x10);
if ((pad_byte & 0xFF) != pad_byte) {
printf("Incorrect value in padbyte argument: \"%s\".\n", &(argv[argn][11]));
printf(" Value cannot be converted to a byte ");
for (int i = 0; i < strlen(&(argv[argn][10])); i++)
printf("^");
printf("\n");
if (switch_pause) getchar();
exit(EXIT_FAILURE);
}
if (next_xarg == &(argv[argn][10])) {
printf("Incorrect byte encoding in padbyte argument: \"%s\".\n", &(argv[argn][10]));
if (switch_pause) getchar();
exit(EXIT_FAILURE);
}
//--badbytes -----------------------------------------------------------
} else if (stricmp(argv[argn], "--badbytes") == 0) {
char_is_bad[0x0] = true;
char_is_bad[0xA] = true;
char_is_bad[0xD] = true;
//--badbytes=XX,XX,... -------------------------------------------------
} else if (strnicmp(argv[argn], "--badbytes=", 11) == 0) {
char* xarg = &(argv[argn][11]);
while (strlen(xarg) > 0) {
if (strnicmp(xarg, "alpha", 5) == 0) {
for (int i = 0; i < 0x100; i++) {
if (!isalnum(i)) char_is_bad[i] = true;
}
xarg += 5;
} else if (strnicmp(xarg, "print", 5) == 0) {
for (int i = 0; i < 0x100; i++) {
if (!isprint(i)) char_is_bad[i] = true;
}
xarg += 5;
} else {
char* next_xarg;
int decoded = strtol(xarg, &next_xarg, 0x10);
if ((decoded & 0xFF) != decoded) {
printf("Incorrect value in badbytes argument: \"%s\".\n", &(argv[argn][11]));
for (char* i = &(argv[argn][9]); i < xarg; i++)
printf(" ");
printf(" Value cannot be converted to a byte ");
for (char* i = xarg; i < next_xarg; i++)
printf("^");
printf("\n");
if (switch_pause) getchar();
exit(EXIT_FAILURE);
}
if (next_xarg == xarg) {
printf("Incorrect byte encoding in badbytes argument: \"%s\".\n", &(argv[argn][11]));
for (char* i = &(argv[argn][11]); i < xarg; i++)
printf(" ");
printf(" Character '%c' not expected ^\n", *xarg);
if (switch_pause) getchar();
exit(EXIT_FAILURE);
}
char_is_bad[decoded] = true;
xarg = next_xarg;
}
if (*xarg == ',') xarg++;
}
А это список ошибок выдаваемых компилятором(не считая варнигов):
Код HTML:
beta.cpp:34:21: windows.h: No such file or directory
beta.cpp: In function `int main(int, char**, char**)':
beta.cpp:165: error: `stricmp' undeclared (first use this function)
beta.cpp:185: error: `strnicmp' undeclared (first use this function)
beta.cpp:245: error: `isalnum' undeclared (first use this function)
beta.cpp:250: error: `isprint' undeclared (first use this function)
beta.cpp:339: error: invalid conversion from `void*' to `char*'
beta.cpp:356: error: `O_BINARY' undeclared (first use this function)
beta.cpp:361: error: `lseek' undeclared (first use this function)
beta.cpp:377: error: invalid conversion from `void*' to `char*'
beta.cpp:384: error: `read' undeclared (first use this function)
beta.cpp:398: error: `close' undeclared (first use this function)
Ну, с ошибкой 34 все понятно — программа усиленно косит под форточки, не понятно за каким хреном таща за собой файл <windows.h>, но тут же использует стандартные POSIX- вызовы: open (со странным флагом O_BINARY), lseek, read и close, которых ни в самом windows, ни в одном из win32-компиляторов никогда не существовало (см. ошибки 356, 361, 384 и 398). Убираем строку "#include <windows.h>", заменяя ее на "#include <unistd.h>" и удаляем глупый флаг O_BINARY, поскольку по умолчанию файл уже является двоичным (узнать какие заголовочные файлы соответствуют данной функции можно из man'а, например, "man 2 open"). Для избавления от ругательства "this file include <malloc.h> witch is deprecated, use <stdlib.h> instead" удаляем и "#include <malloc.h>".
Ошибки 245 и 250 устраняются подключением их "родного" заголовочного файла, в котором они были объявлены "#include <ctype.h>" (см. "man isalnum"). А вот функций stricmp и strnicmp в gcc действительно нет, однако, они могут быть заменены на аналогичные им strcmp и strncmp даже без коррекции аргументов!
Ошибки 339 и 377 исправляется еще проще: достаточно взять строку "buffer=malloc(MAX_BUFFER_SIZE)" и добавить явное преобразование типов, так же называемое ксатингом: "buffer=(char*)malloc(MAX_BUFFER_SIZE)".
Еще дополню разбором shell-кода, как только воткну в него =)
|
|
|

09.07.2008, 20:43
|
|
Moderator - Level 7
Регистрация: 16.02.2008
Сообщений: 580
Провел на форуме: 1595333
Репутация:
291
|
|
так же называемое ксатингом:
может все таки кастом?
зы кастинг - подбор актеров (*
|
|
|

09.07.2008, 20:49
|
|
Постоянный
Регистрация: 30.04.2008
Сообщений: 323
Провел на форуме: 379101
Репутация:
136
|
|
beta.cpp:34:21: windows.h: No such file or directory
Вообще зверь. Может нормально отредактируешь точто находится зеленым цветом, вплотную не соблюдая абзацов. И вообще обычное дело доработка эксплоитов, КК писал об этом. В любом случае молодец.
|
|
|

02.01.2009, 07:54
|
|
Постоянный
Регистрация: 13.02.2007
Сообщений: 406
Провел на форуме: 2706059
Репутация:
206
|
|
сишные трюки
сишные трюки
(1Fh выпуск)
язык си не предоставляет никаких средств для временного отключения блоков кода и большинство программистов делают это с помощью комментариев. казалось бы что может быть проще и о каких трюках тут вообще говорить? на самом же деле, комментарии не только не единственный, но и едва ли не самый худший прием среди прочих о которых мы сейчас и поговорим!
трюк #1*– комментарии, ремарки и помарки
Системы контроля версий как раз и создавались для того, чтобы обеспечить легкий, прозрачный и непротиворечивый механизм безопасной правки исходных текстов инвариантный по отношению к самому языку. Однако, на практике системы контроля версий используются только для организации совместной работы над проектом, да и то не всегда. Уж слишком много телодвижений приходится совершать всякий раз, а программисты*— люди ленивые.
Если нам необходимо временно отключить блок кода, намного проще закомментировать его, а потом удалить комментарии, подключая его обратно. Быстро. Дешево. Сердито. Но увы… потенциально небезопасно с точки зрения внесения новых ошибок и развала уже отлаженной программы, чего допускать ни в коем случае нельзя. А потому прежде, чем идти дальше, сформулируем перечень требований, предъявляемый к механизмам отключения кода:
- легкость использования (никто не будет пользовать средство, требующее кучи телодвижений);
- вложенность (внутри отключаемого блока может находится один или несколько ранее отключенных блоков);
- многоуровневость (если для отключения блока кода необходимо исправить два и более несмежных фрагментов исходного текста, необходимо гарантировать корректное снятие блокировки, что становится особенно актуально, если отключаются независимые блоки А, B, С*– тогда, при включении блока B возникает угроза подключения фрагментов, относящихся к блокам A и C, что ведет к развалу программы);
- поддержка всех языковых конструкций (какой прок от инструмента, если он работает только с ограниченным набором языковых конструкций, например, не позволяет отключать ассемблерные вставки?!);
Удовлетворяют ли комментарии указанным требованиям?! А вот и нет! Комментарии в стиле Си (/* */) очень удобны, поскольку, позволяют отключать огромные блоки кода нажатием всего четырех клавиш, к тому же они могут располагаться в любом месте строки, а не только в ее начале. Однако, отсутствие поддержки вложенности создает серьезные проблемы.
Например:
Код:
/* ошибка! закомментированный блок уже содержит /* */
for (a = 0; a < N; a++)
{
/*
for (b = 0; b < M; b++)
if (!strcmp(name_array[a], vip_array)) continue;
*/
// DeleteFile(name_array[a]);
pritnf("%d %s\n", a, name_array[a]);
}
*/
Листинг*1*демонстрация некорректного использования комментариев /* */ для временного отключения блоков кода
Попытка выключить цикл for (a,,) ведет к ошибке компиляции*— комментарии /* */ не могут быть вложенными и в таких случаях программисты используют альтернативу в виде "//" допускающую вложенность, но, увы, вручную проставляемую вначале _каждой_ строки, что очень утомительно и совершенно непроизводительно, если, конечно, не использовать макросы, поддерживаемые средой разработки (а практически все среды разработки их поддерживают). Аналогичным образом осуществляется и снятие комментариев.
И все было бы хорошо, да вот неоднозначности с уровнем вложенности делают отключение блоков небезопасным. В нашем случае мы имеем три раздельных отключаемых блока кода. Во-первых, это заблокированная проверка принадлежности удаляемого файла к vip_array, во-вторых, это, собственно, само удаление файла (заблокированное и замененное отладочной печатью через printf) и, в-третьих, комментарий, пытающийся отключить цикл for(a,,) со всем что в нем находится.
Отключаются блоки кода очень просто, а вот обратное утверждение уже неверно. Никаким автоматизмом тут уже и не пахнет, в результате чего нам приходится разбираться с назначением каждого блока самостоятельно. Однако, если немного поколдовать над комментариями…
Пусть следом за "//" идет цифра (или буква) указывающая принадлежность текущей комментируемой строки к блоку кода. Продвинутые среды разработки типа Microsoft Visual Studio поддерживают развитый макроязык, позволяющий выполнять лексический анализ, удаляя только те комментарии, за которыми идет заданная буква/цифра.
Это может выглядеть, например, так:
Код:
//3 for (a = 0; a < N; a++)
//3 {
//3 //2
//3 //2 for (b = 0; b < M; b++)
//3 //2 if (!strcmp(name_array[a], vip_array[b])) continue;
//3 //2
//3 //1 // DeleteFile(name_array[a]);
//3 pritnf("%d %s\n", a, name_array[a]);
//3 }
Листинг*2*имитация многоуровневой структуры отключаемых блоков исходного кода посредством комментариев
Проблема вложенности решена на 100%, проблема многоварианости*— на 50% (после удаления комментария //1 мы так же должны удалить, а точнее временно заблокировать следующую за ним строку с отладочной печатью), однако, в целом предложенная техника намного более удобна и единственный серьезный недостаток*— привязка программиста к конкретной среде с набором пользовательских макросов. Менее серьезный недостаток*— ассемблерные вставки как правило не поддерживают Си/Си++ комментариев и потому должны обрабатываться отдельно, усложняя реализацию нашего макродвижка и сводя его преимущества на нет.
трюк #2*— директивы условной трансляции
Разработанные для поддержки многовариантного кода директивы условной трансляции оказались практически невостребованными (речь, разумеется, идет только о временном выключении кода), что очень странно*— директивы условной трансляции намного более эффективны, чем комментарии и пример, приведенный ниже, доказывает этот тезис.
Код:
#define _D1_ // блок _D1_ включен
//#define _D2_ // блок _D2_ выключен
#define _D3_ // блок _D3_ включен
#ifdef _D1_
for (a = 0; a < N; a++)
{
#ifdef _D2_
for (b = 0; b < M; b++)
if (!strcmp(name_array[a], vip_array[b])) continue;
#endif
#ifdef _D3_
DeleteFile(name_array[a]);
#else
pritnf("%d %s\n", a, name_array[a]);
#endif
}
#endif
Листинг*3*директивы препроцессора, отключающие блоки кода
Проблема вложенности решается сама собой, многовариантность поддерживается очень хорошо, позволяя нам включать/выключать определенные блоки, не затрагивая остальных, причем, при подключении "DeleteFile(name_array[a])"*— автоматически отключается отладочная печать и наоборот. В результате чего риск развала программы уменьшается до нуля. Самое интересное, что директивы условной трансляции ничуть не хуже работают и с ассемблерными вставками!
Код:
__asm{
xor eax,eax
#ifdef _D1_
PUSH file_name
CALL DeleteFile
#endif
}
Листинг*4*директивы препроцессора, отключающие ассемблерные инструкции _внутри_ ассемблерных вставок
Конечно, писать "#if def _Dx_" намного длиннее, чем "//" или "/* */", однако, это не проблема*— клавиатурные макросы на что?! Хотя про нежелание связаться с макросами мы уже говорили. Ну макросы это ладно. Хуже всего, что отключенные блоки кода не попадают в релиз, и если у конечного пользователя программа начнет дико глючить у нас не будет никакой возможности отключить их без перекомпиляции всего кода.
трюк #3*– ветвления
Финальный прием устраняет основные недостатки предыдущего трюка, добавляя к нему свои собственные достоинства, а достоинств у него… Короче, намного больше одного. Идея заключается в использовании конструкции if (_Dx_), а при необходимости и if (_Dx_) else.
Оператор "if", стоящий перед одиночным блоком кода, не требует замыкающего "#endif", что ускоряет процесс программирования и не так сильно загромождает листинг. Но это мелочь. Гораздо важнее, что если _Dx_ константа (например, "1"), то оптимизирующий компилятор выбрасывает вызов if, удаляя лишний оверхид. Если же _Dx_ переменная (глобальная, конечно), то компилятор оставляет ветвление "как есть", давая нам возможность управлять поведением программы*— если у пользователей возникнут проблемы из-за ошибки в плохо отлаженном блоке кода, то этот блок можно отключить (естественно, если значения флагов вынесены в конфигурационный файл или доступны через пользовательский интерфейс, но это уже несущественные детали реализации).
Пример использования ветвлений для отключения блоков кода приведен ниже:
Код:
#define _D1_ 0 // блок _D1_ выключен (ветвление в релиз не попадает)
#define _D3_ 1 // блок _D3_ включен (ветвление в релиз не попадает)
int _D2_ 1 // блок _D2_ включен (ветвление попадает в релиз!)
if (_D1_)
for (a = 0; a < N; a++)
{
if (_D2_)
for (b = 0; b < M; b++)
if (!strcmp(name_array[a], vip_array[b])) continue;
if (_D3_)
DeleteFile(name_array[a]);
else
pritnf("%d %s\n", a, name_array[a]);
}
Листинг*5*использование ветвлений для выключения блоков кода
Как мы видим, листинг*5 намного компактнее и нагляднее листинга*4, так что при всем уважении к директивам условной трансляции, они идут лесом. А вот ветвления можно использовать для выключения блока ассемблерных вставок (о чем кстати говоря, умалчивает штатная документация, но следующий пример компилируется вполне нормально):
Код:
#define _D1_ 0
if (_D1_)
__asm{
INT 03
}
Листинг*6*использование ветвлений для выключения ассемблерных вставок
Ветвления, конечно, тоже не лишены недостатков, однако, для временного выключения блоков кода они намного лучше, удобнее и продуктивнее, чем комментарии. Естественно, существуют и другие средства. Взять хотя бы "return", позволяющий одним движением руки погасить блок кода до самого конца функции. Критикуемый GOTO*– отличная штука, но только в малых дозах. Иначе программа превращается в настоящее спагетти, которое практически невозможно распутать.
(c)крис касперски ака мыщъх, a.k.a. souriz, a.k.a. nezumi, no-email
|
|
|

20.03.2009, 22:36
|
|
Познавший АНТИЧАТ
Регистрация: 01.06.2008
Сообщений: 1,047
Провел на форуме: 5321514
Репутация:
3313
|
|
Читаю, где-то я это читал - Крис Касперски - понятно .(напишите его имя в копирайтах с заглавной буквы)
У кого - нибудь есть ссылки на его статьи по C? Можете поделиться?
Последний раз редактировалось Chaak; 20.03.2009 в 22:45..
|
|
|

21.03.2009, 03:17
|
|
Новичок
Регистрация: 17.02.2009
Сообщений: 13
Провел на форуме: 72422
Репутация:
8
|
|
Последний раз редактировалось h01der; 21.03.2009 в 18:02..
|
|
|

21.03.2009, 13:00
|
|
Banned
Регистрация: 06.01.2008
Сообщений: 904
Провел на форуме: 4037638
Репутация:
1821
|
|
Сообщение от Chaak
Читаю, где-то я это читал - Крис Касперски - понятно .(напишите его имя в копирайтах с заглавной буквы)
У кого - нибудь есть ссылки на его статьи по C? Можете поделиться?
весь хакер(журнал) забит ими, интересно кстати почитать.
|
|
|

21.03.2009, 14:25
|
|
Новичок
Регистрация: 17.02.2009
Сообщений: 13
Провел на форуме: 72422
Репутация:
8
|
|
Ну вот я их выложил в посте выше...
|
|
|
|
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|