Blog. Just Blog

Исследование защиты программы Restorator

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Темная сторона Силы | Автор: ManHunter
Скриншот программы Restorator
Скриншот программы Restorator

Restorator - один из самых известных редакторов ресурсов для Windows. Основная фишка, которая мне в нем нравилась больше всего - это возможность создания автономных патчей ресурсов, например, для русификации программ. Но этим, конечно же, список его достоинств не ограничивается. К недостаткам могу отнести, пожалуй, весьма специфический интерфейс для работы с файлами и необходимость выкладывать деньги за лицензию. Много лет этот редактор не обновлялся, а тут вдруг разработчики решили допилить его под современные реалии. Так что эта статья посвящается возвращению легенды.

Как обычно, первым делом забираем с офсайта дистрибутив. На сайте есть еще триальная версия, она не регистрируется и ее качать не надо. Устанавливаем, запускаем. Программа с ходу встречает нас окном с предложением ввести ключ. Без этого ничего не сделать.

Триальное окно
Триальное окно

Ладно, файл ничем не упакован, отправляем его в дизассемблер. А пока давайте поищем в файле что-нибудь, относящееся к этому окну, например, заголовок. В ресурсах обнаружится следующая строчка.

Строка в ресурсах
Строка в ресурсах

Ее индекс 64778 в десятичной или 0FD0Ah в шестнадцатеричной системе счисления. Поищем индекс в листинге.
  1. .text:0054B094 off_54B094      dd offset hModule
  2. .text:0054B098                 dd 0FD0Ah
Знакомая конструкция - указатель на указатель. По перекрестной ссылке выходим на код, где этот указатель используется.
  1. .text:0054B09C sub_54B09C      proc near               ; CODE XREF: sub_54B110+5 p
  2. .text:0054B09C var_4           = dword ptr -4
  3. .text:0054B09C                 push    ebp
  4. .text:0054B09D                 mov     ebp, esp
  5. .text:0054B09F                 push    0
  6. .text:0054B0A1                 push    ebx
  7. .text:0054B0A2                 mov     ebx, eax
  8. .text:0054B0A4                 xor     eax, eax
  9. .text:0054B0A6                 push    ebp
  10. .text:0054B0A7                 push    offset loc_54B102
  11. .text:0054B0AC                 push    dword ptr fs:[eax]
  12. .text:0054B0AF                 mov     fs:[eax], esp
  13. .text:0054B0B2                 lea     edx, [ebp+var_4]
  14. .text:0054B0B5                 mov     eax, offset off_54B094
  15. .text:0054B0BA                 call    @System@LoadResString
  16. .text:0054B0BF                 mov     edx, [ebp+var_4]
  17. .text:0054B0C2                 mov     eax, ebx
  18. .text:0054B0C4                 call    @Controls@TControl
  19. .text:0054B0C9                 xor     edx, edx
  20. .text:0054B0CB                 mov     eax, [ebx+368h]
  21. .text:0054B0D1                 call    @Extctrls@TNotebook
  22. .text:0054B0D6                 mov     eax, [ebx+360h]
  23. .text:0054B0DC                 mov     [eax+114h], ebx
  24. ...
Тут только загрузка строк и подготовка формы. Идем выше к месту, откуда эта функция вызывается.
  1. .text:0054B110 sub_54B110      proc near
  2. .text:0054B110                 push    ebx
  3. .text:0054B111                 mov     ebx, eax
  4. .text:0054B113                 mov     eax, ebx
  5. .text:0054B115                 call    sub_54B09C
  6. .text:0054B11A                 mov     eax, ebx
  7. .text:0054B11C                 mov     edx, [eax]
  8. .text:0054B11E                 call    dword ptr [edx+0FCh]
  9. .text:0054B124                 pop     ebx
  10. .text:0054B125                 retn
  11. .text:0054B125 sub_54B110      endp
Тоже ничего интересного. Тут подготовленная форма выводится на экран. Дальше.
  1. .text:005754AD                 mov     eax, off_599548
  2. .text:005754B2                 mov     eax, [eax]
  3. .text:005754B4                 call    sub_4DB038
  4. ; Указатель на блок параметров
  5. .text:005754B9                 mov     eax, off_599548
  6. .text:005754BE                 mov     eax, [eax]
  7. ; Байт по смещению 15h должен быть ненулевым, иначе вывести окно регистрации
  8. .text:005754C0                 cmp     byte ptr [eax+15h], 0
  9. .text:005754C4                 jnz     short loc_57550C
  10. .text:005754C6                 mov     ecx, off_5999DC
  11. .text:005754CC                 mov     ecx, [ecx]
  12. .text:005754CE                 mov     dl, 1
  13. .text:005754D0                 mov     eax, ds:off_54A858
  14. .text:005754D5                 call    @Forms@TCustomForm
  15. .text:005754DA                 mov     edx, off_5997D8
  16. .text:005754E0                 mov     [edx], eax
  17. .text:005754E2                 mov     eax, off_5997D8
  18. .text:005754E7                 mov     eax, [eax]
  19. .text:005754E9                 call    sub_54B110
  20. .text:005754EE                 mov     eax, off_599548
  21. .text:005754F3                 mov     eax, [eax]
  22. .text:005754F5                 cmp     byte ptr [eax+15h], 0
  23. .text:005754F9                 jnz     short loc_57550C
  24. .text:005754FB                 mov     eax, off_5999DC
  25. .text:00575500                 mov     eax, [eax]
  26. .text:00575502                 call    @Forms@TApplication@Terminate
  27. .text:00575507                 jmp     loc_575645
  28. .text:0057550C ; ---------------------------------------------
  29. .text:0057550C loc_57550C:
  30. .text:0057550C                 cmp     byte_5992FC, 0
  31. .text:00575513                 jz      short loc_575534
  32. .text:00575515                 mov     eax, off_5999DC
Бинго! В регистр EAX загружается указатель на некий блок данных, как мне кажется, что-то типа настроек программы, один байт из которых отвечает за то, чтобы показывать при запуске форму регистрации или нет. Есть предложение помочь программе с этим :) Для этого команду cmp byte ptr [eax+15h], 0 по адресу 005754C0 надо заменить на равную ей по размеру команду mov byte ptr [eax+15h], 1, а следующий за ней условный переход на безусловный. Тем самым мы взведем флаг зарегистрированности, который будет использоваться в дальнейшем, а также сразу перепрыгнем форму регистрации. Сохраняем изменения, запускаем, ииии.....

Собщение о поврежденном файле
Собщение о поврежденном файле

Где-то выполняется проверка целостности исполняемого файла. Мы его пропатчили, стало быть, целостность нарушена. Давайте поищем, где находится строка этого сообщения.

Строка сообщения в ресурсах
Строка сообщения в ресурсах

Вот, строка найдена в ресурсах. Ее индекс 64581 в десятичной или 0FC45h в шестнадцатеричной системе счисления. Поищем индекс в листинге.
  1. .text:0057512C off_57512C      dd offset hModule
  2. .text:00575130                 dd 0FC45h
По перекрестным ссылкам выходим на следующий код:
  1. .text:0058204B                 call    sub_5076E0
  2. .text:00582050                 test    al, al
  3. .text:00582052                 jnz     short loc_582075
  4. .text:00582054                 lea     edx, [ebp+var_C]
  5. .text:00582057                 mov     eax, offset off_57512C
  6. .text:0058205C                 call    @System@LoadResString
  7. .text:00582061                 mov     ecx, [ebp+var_C]
  8. .text:00582064                 mov     dl, 1
  9. .text:00582066                 mov     eax, ds:off_408858
  10. .text:0058206B                 call    unknown_libname_157
  11. .text:00582070                 call    @System@@RaiseExcept$qqrv
Все понятно. Выполняется проверка, если ее результат программу не удовлетворил, то выводится сообщение и бросается исключение, приводящее к завершению программы. Патчим условный переход по адресу 00582052 на безусловный, сохраняем изменения, запускаем.

Невозможно создать патч
Невозможно создать патч

Внешне все работает, но при попытке создать автономный патч ресурсов проявляется еще одно ограничение.

Строка сообщения в ресурсах
Строка сообщения в ресурсах

Строка сообщения найдена в ресурсах. Точнее, таких строк там две, нас интересует вторая. По ее индексу выходим на следующий указатель:
  1. .text:0056A5C0 off_56A5C0      dd offset X
  2. .text:0056A5C4                 dd 0FC9Ah
А на этот указатель ссылается следующий код:
  1. .text:0056A64F                 call    @Registry@TRegistry@ValueExists
  2. .text:0056A654                 test    al, al
  3. .text:0056A656                 jz      short loc_56A67A
  4. .text:0056A658                 mov     edx, offset _str_Password.Text
  5. .text:0056A65D                 mov     eax, [ebp+var_18] ; this
  6. .text:0056A660                 call    @Registry@TRegistry@ValueExists
  7. .text:0056A665                 test    al, al
  8. .text:0056A667                 jz      short loc_56A67A
  9. .text:0056A669                 mov     edx, offset _str_Type.Text
  10. .text:0056A66E                 mov     eax, [ebp+var_18] ; this
  11. ; Проверить наличие ключа в реестре
  12. .text:0056A671                 call    @Registry@TRegistry@ValueExists
  13. .text:0056A676                 test    al, al
  14. .text:0056A678                 jnz     short loc_56A67E
  15. .text:0056A67A loc_56A67A:
  16. ; Ключа нет, программа не зарегистрирована
  17. .text:0056A67A                 xor     eax, eax
  18. .text:0056A67C                 jmp     short loc_56A680
  19. .text:0056A67E ; ---------------------------------------
  20. .text:0056A67E loc_56A67E:
  21. ; Ключ в наличии, программа зарегистрирована
  22. .text:0056A67E                 mov     al, 1
  23. .text:0056A680 loc_56A680:
  24. ; Программа зарегистрирована?
  25. .text:0056A680                 test    al, al
  26. ; Да, продолжить работу
  27. .text:0056A682                 jnz     short loc_56A6A5
  28. .text:0056A684                 lea     edx, [ebp+var_28]
  29. ; Загрузить строку сообщения из ресурсов
  30. .text:0056A687                 mov     eax, offset off_56A5C0
  31. .text:0056A68C                 call    @System@LoadResString
  32. .text:0056A691                 mov     ecx, [ebp+var_28]
  33. .text:0056A694                 mov     dl, 1
  34. .text:0056A696                 mov     eax, ds:off_408858
  35. .text:0056A69B                 call    unknown_libname_159
  36. ; Бросить исключение
  37. .text:0056A6A0                 call    @System@@RaiseExcept$qqrv
Тут проверяется наличие в реестре регистрационного ключа. Чтобы не шаманить раньше времени с реестром, пропатчим код, чтобы он работал независимо от наличия ключа. Например, заменим команду по адресу 0056A67A на MOV AL,1 или заменим условный переход по адресу 0056A682 на безусловный. Сохраняем изменения, пробуем теперь создать автономный патч ресурсов. Отлично, теперь программа работает в полноценном режиме, все функции доступны, никаких ограничений нет.

Остался еще один момент. Хоть все функциональные ограничения сняты и программа запускается, в окне "О программе" и сплеш-окне, которое появляется при старте, все равно наблюдается надпись "Trial Version":

Триальная надпись
Триальная надпись

Оно вроде и не мешается, и вообще, сплеш-окно можно отключить в настройках, но хочется, чтобы все было красиво.

Строка в ресурсах
Строка в ресурсах

Находим строку в ресурсах, по индексу находим указатель на указатель.
  1. .text:00573440 off_573440      dd offset hModule
  2. .text:00573444                 dd 0FC64h
В листинге находим следующий код.
  1. ; Сравнить переменную с нулевым значением
  2. .text:005735FE                 cmp     [ebp+var_11], 0
  3. .text:00573602                 jz      short loc_573641
  4. ; Если она не равна 0, то вывести строку "Licended to.."
  5. .text:00573604                 lea     eax, [ebp+var_24]
  6. .text:00573607                 push    eax
  7. .text:00573608                 lea     edx, [ebp+var_28]
  8. .text:0057360B                 mov     eax, offset off_573448
  9. .text:00573610                 call    @System@LoadResString
  10. .text:00573615                 mov     eax, [ebp+var_28]
  11. .text:00573618                 push    eax
  12. .text:00573619                 mov     eax, [ebp+var_4]
  13. .text:0057361C                 mov     [ebp+var_30], eax
  14. .text:0057361F                 mov     [ebp+var_2C], 0Bh
  15. .text:00573623                 lea     edx, [ebp+var_30]
  16. .text:00573626                 xor     ecx, ecx
  17. .text:00573628                 pop     eax
  18. .text:00573629                 call    unknown_libname_127
  19. .text:0057362E                 mov     edx, [ebp+var_24]
  20. .text:00573631                 mov     eax, [ebp+var_8]
  21. .text:00573634                 mov     eax, [eax+37Ch]
  22. .text:0057363A                 call    @Controls@TControl@SetText
  23. .text:0057363F                 jmp     short loc_57365F
  24. .text:00573641 ; ---------------------------------------
  25. .text:00573641 loc_573641:
  26. ; Если 0, то строка будет "Trial version"
  27. .text:00573641                 lea     edx, [ebp+var_34]
  28. .text:00573644                 mov     eax, offset off_573440
  29. .text:00573649                 call    @System@LoadResString
  30. .text:0057364E                 mov     edx, [ebp+var_34]
  31. .text:00573651                 mov     eax, [ebp+var_8]
  32. .text:00573654                 mov     eax, [eax+37Ch]
  33. .text:0057365A                 call    @Controls@TControl
Надо выяснить, где инициализируется переменная, которая участвует в этой проверке. Листаем выше:
  1. .text:00573555                 push    [ebp+var_20]
  2. .text:00573558                 push    offset stru_573898.Text
  3. .text:0057355D                 push    offset _str_Registration_0.Text
  4. .text:00573562                 lea     eax, [ebp+var_1C]
  5. .text:00573565                 mov     edx, 3
  6. .text:0057356A                 call    @System@@LStrCatN$qqrv
  7. .text:0057356F                 mov     edx, [ebp+var_1C]
  8. .text:00573572                 xor     ecx, ecx
  9. .text:00573574                 mov     eax, [ebp+var_18]
  10. .text:00573577                 call    @Registry@TRegistry@OpenKey
  11. .text:0057357C                 test    al, al
  12. .text:0057357E                 jz      short loc_5735A2
  13. .text:00573580                 mov     edx, offset _str_Name_2.Text
  14. .text:00573585                 mov     eax, [ebp+var_18]
  15. .text:00573588                 call    @Registry@TRegistry@ValueExists
  16. .text:0057358D                 test    al, al
  17. .text:0057358F                 jz      short loc_5735A2
  18. .text:00573591                 mov     edx, offset _str_Password_0.Text
  19. .text:00573596                 mov     eax, [ebp+var_18]
  20. .text:00573599                 call    @Registry@TRegistry@ValueExists
  21. .text:0057359E                 test    al, al
  22. .text:005735A0                 jnz     short loc_5735A6
  23. .text:005735A2 loc_5735A2:
  24. .text:005735A2                 xor     eax, eax
  25. .text:005735A4                 jmp     short loc_5735A8
  26. .text:005735A6 ; ---------------------------------------
  27. .text:005735A6 loc_5735A6:
  28. .text:005735A6                 mov     al, 1
  29. .text:005735A8 loc_5735A8:
  30. .text:005735A8                 mov     [ebp+var_11], al
По виду ничего сложного. Проверяется наличие определенных ключей реестра, если хоть одного не обнаружится, то выводится триальная строка. С помощью любого монитора реестра или под отладчиком выясням, какие именно ветки реестра задействуются. Это будет ключ HKEY_CURRENT_USER\Software\Bome Software\Restorator\Registration и параметры "Name" и "Password". Нетрудно понять, что в параметре "Name" хранится регистрационное имя, а в параметре "Password" что-то типа серийного номера.

Данные хранятся в реестре, так что на новой машине придется повторять процесс заново. Поэтому для удобства можно оформить регистрацию в виде reg-файла.

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Bome Software\Restorator\Registration]
"Name"="ManHunter / PCL"
"Password"="Fuck Shareware"

Последняя контрольная проверка - все работает.

Программа успешно "зарегистрирована"
Программа успешно "зарегистрирована"

Но это еще не все. С самых первых версий Restorator имеет дурную привычку помечать отредактированные ресурсы своей меткой "Bome". Вроде ничего смертельного, но неприятно. Давайте исправим это недоразумение. Маркеры формируются по кусочкам из двух WORD'ов:
  1. .text:005080C4                 call    sub_50880C
  2. .text:005080C9                 test    al, al
  3. .text:005080CB                 jz      short loc_5080E5
  4. .text:005080CD                 mov     eax, [ebp+arg_0]
  5. .text:005080D0                 mov     eax, [eax-8]
  6. .text:005080D3                 mov     word ptr [eax+8], 6F42h ; 'Bo'
  7. .text:005080D9                 mov     eax, [ebp+arg_0]
  8. .text:005080DC                 mov     eax, [eax-8]
  9. .text:005080DF                 mov     word ptr [eax+0Ah], 656Dh ; 'me'
  1. .text:00508183                 call    sub_5089B0
  2. .text:00508188                 test    al, al
  3. .text:0050818A                 jz      short loc_5081A4
  4. .text:0050818C                 mov     eax, [ebp+arg_0]
  5. .text:0050818F                 mov     eax, [eax-8]
  6. .text:00508192                 mov     word ptr [eax+8], 6F42h ; 'Bo'
  7. .text:00508198                 mov     eax, [ebp+arg_0]
  8. .text:0050819B                 mov     eax, [eax-8]
  9. .text:0050819E                 mov     word ptr [eax+0Ah], 656Dh ; 'me'
И еще какая-то странная конструкция, в которой тоже по кусочкам упоминается указанная строка
  1. .text:00508954                 mov     eax, [eax+4]
  2. .text:00508957                 cmp     dword ptr [eax+18h], 0
  3. .text:0050895B                 jz      short loc_508977
  4. .text:0050895D                 mov     edx, [eax+18h]
  5. .text:00508960                 cmp     word ptr [edx+8], 6F42h ; 'Bo'
  6. .text:00508966                 jnz     short loc_508970
  7. .text:00508968                 cmp     word ptr [edx+0Ah], 656Dh ; 'me'
  8. .text:0050896E                 jz      short loc_508974
В первых двух случая заменяем условные переходы по адресам 005080CB и 0050818A на безусловные, тем самым навсегда заблокировав несанкционированную маркировку ресурсов. В третьем кусочке кода аналогично меняется безусловный переход по адресу 0050895B.

Вот и все, программа работает, в реестре прописано правильное регистрационное имя, косметические изменения внесены, а жизнь прекрасна. Цель достигнута. Хотя, как мне кажется, подобные программы должны быть портативными "из коробки".

Поделиться ссылкой ВКонтакте
Просмотров: 3155 | Комментариев: 22

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Александр (06.06.2024 в 05:30):
==DJ==[ZLO], А разве не за счёт этих меток определяется что ресурс был изменён?
В Restorator 2007 отключается в настройках: Сохранение файлов ? Создавать метки на изменённых ресурсах (стрелочка).
Полагаю что в версии 2018 аналогично.
ManHunter (20.02.2024 в 16:59):
ida pro
Анд (20.02.2024 в 16:58):
Добрый день, какой программой дезассемблировали в листингах?
ManHunter (01.12.2020 в 15:23):
Добавил все патчи из камментов в статью.
ManHunter (26.07.2019 в 16:59):
.text:0050818A  jz      short loc_5081A4
такая же конструкция

и для профилактики
.text:0050895B  jz      short loc_508977
не знаю, что это делает, но лучше предусмотреть
==DJ==[ZLO] (26.07.2019 в 16:56):
Оперативно. Но, он даже при таком патче вешает метку.
.text:005080CB                 jmp     short loc_5080E5
.text:005080CD                 mov     eax, [ebp+arg_0]
.text:005080D0                 mov     eax, [eax-8]
.text:005080D3                 mov     word ptr [eax+8], 0
.text:005080D9                 mov     eax, [ebp+arg_0]
.text:005080DC                 mov     eax, [eax-8]
.text:005080DF                 mov     word ptr [eax+0Ah], 0
http://prntscr.com/okevmv
ManHunter (26.07.2019 в 16:21):
.text:005080C4  call    sub_50880C
.text:005080C9  test    al, al
.text:005080CB  jz      short loc_5080E5  <---- jmp
.text:005080CD  mov     eax, [ebp+arg_0]
.text:005080D0  mov     eax, [eax-8]
.text:005080D3  mov     word ptr [eax+8], 6F42h ; 'Bo'
.text:005080D9  mov     eax, [ebp+arg_0]
.text:005080DC  mov     eax, [eax-8]
.text:005080DF  mov     word ptr [eax+0Ah], 656Dh ; 'me'
==DJ==[ZLO] (26.07.2019 в 16:12):
Приветствую вас Дмитрий. Все получилось. Но, есть маленькая неприятность Помогите избавиться от дополнительного мусора который ресторатор оставляет после себя. 20шт(у меня) меток "Bome".
http://prntscr.com/oke28n
Андрей (26.03.2019 в 14:59):
всё супер. огромное спасибо.
http://prntscr.com/n33mcm
ManHunter (26.03.2019 в 13:43):
.text:0056A67A xor eax, eax <-- mov al,1
Андрей (26.03.2019 в 12:55):
маленькая поправочка.
наг окошко было на русском языке. возможно с руссиком что-то было не так.
но и без всяких других patchs тоже самое.
P.S. через ключ такое не выскакивает.
http://prntscr.com/n31ogw

ManHunter (26.03.2019 в 12:40):
Регистрацию в реестр занес?
- да, конечно.
http://prntscr.com/n31q19
ManHunter (26.03.2019 в 12:40):
Регистрацию в реестр занес?
Андрей (26.03.2019 в 05:12):
добрый день.
всё супер. окно регистрации пропало. в "About" тоже всё хорошо.
но где-то не срослось, может у меня...
при попытке создать patch через Restorator вылезло плохое окно. удалил и заного поставил Restorator с чисткой реестра и с регистрацией от ключа. Patch вышел отлично. делал patch регистрации через dUP2(скрины прилагаю). проверьте кто так-же попробовал зарегать. а так всё супер, сайт нравиться и читаю с удовольствием.
http://prntscr.com/n2x2mi
http://prntscr.com/n2x2xg
http://prntscr.com/n2x4n0
ManHunter (17.09.2018 в 08:33):
С таким уровнем рановато браться за взломы. Подтяни теорию, через пару лет возвращайся.
Сергей (17.09.2018 в 05:54):
Здравствуйте
Можно несколько вопросов от новичка?
Как определили что индекс 64581 в десятичной в шестнадцатеричной системе будет 0FC45h
Что такое листинг и как в нем искать
Как патчить условный переход по адресу 00582052 на безусловный

Спасибо
pawel97 (08.08.2018 в 17:39):
p.s. Не, crc всё равно патчить. Первый раз при изменении 5754C0 в памяти (не в файле) вылезло сообщение, а 4db44d нет, может глюк... Не важно, будет 2-й вариант.
pawel97 (08.08.2018 в 16:55):
Как вариант - этот кусок под проверку целостности не попал, и выполняется чуть раньше, и патчить чуть меньше:
004DB44D      FE40 15       inc byte ptr ds:[eax+15]
Вышел хардварным бряком на нужный байт.
Styx (07.08.2018 в 15:11):
Спасибо за еще одно руководство к действию! :)
Прошел немного дальше и записал данные регистрации в исполняемом файле, чтобы не вносить их каждый раз в реестр.
xussr (06.08.2018 в 12:37):
Классика!!!!(какие дрова)
Спасибо за прекрасный урок
Владимир (06.08.2018 в 11:26):
в процессе исследования столкнулись с отслеживанием, ведь программа могла быть не знаю как правильно сказать оболочкой для драйвера, просто есть прога, которая общается с драйвером, а процмон нифига не показывает
ManHunter (06.08.2018 в 10:53):
Process Monitor. Только какое отношение это имеет к статье?
Владимир (06.08.2018 в 08:59):
Спасибо! А если к реестру обращается драйвер, уже никак не отследить?

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0075 сек.) / Память: 4.5 Mb
Наверх