Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "WinAPI";
Текущий архив: 2006.04.23;
Скачать: [xml.tar.bz2];

Вниз

Запуск другого приложения из TMemoryStream...   Найти похожие ветки 

 
Stealth   (2006-02-05 12:45) [0]

Hi ALL!

Задача вот в чем:
есть стороннее приложение, не защищенное от копирования. необходимо реализовать для него такую защиту, но только конечно же не от супер хакеров, а от людей, которые хотят просто его переписать и использовать...
Мне на ум пришло вот какое решение:
Сам файл приложения, немного закодировать, поменять его расширение, а вместо него поставить свое приложение, которое бы осуществляло все проверки на защиту, и если все ок, то считывало этот файл в TMemoryStream, декодировало его и запускало...
Но только на диск декодированый файл сохранять не хочется...
Вот собственно и вопрос: Можно ли запустить приложение прямо из TMemoryStream, не сохраняя его на диске???

PS Может кто и другой вариант выполнения данной задачи подскажет???


 
kaZaNoVa ©   (2006-02-05 13:34) [1]

Stealth   (05.02.06 12:45)
PS Может кто и другой вариант выполнения данной задачи подскажет???

подумать надо..


> Можно ли запустить приложение прямо из TMemoryStream

можно.. нам всё можно .. (с) мну

запуск из ресурса:

program project2;

uses
 Windows,
 sysutils,
 rxtypes in "Rxtypes.pas";

{$R rcx.res}

Var
nb, i: Cardinal;

function ZwUnmapViewOfSection(SectionHandle: THandle;
 p: Pointer): DWord; stdcall; external "ntdll.dll";

function protect(characteristics: ULONG): ULONG;
const  mapping: array [0..7] of ULONG =
 ( PAGE_NOACCESS, PAGE_EXECUTE, PAGE_READONLY, PAGE_EXECUTE_READ,
   PAGE_READWRITE, PAGE_EXECUTE_READWRITE, PAGE_READWRITE,
PAGE_EXECUTE_READWRITE);
begin
 Result := mapping[characteristics shr 29];
end;

var
 pi: TProcessInformation;
 si: TStartupInfo;
 x, p, q: Pointer;
 nt: PIMAGE_NT_HEADERS;
 context: TContext;
 sect: PIMAGE_SECTION_HEADER;
begin
 si.cb := SizeOf(si);
 CreateProcess(nil, "cmd.exe", nil, nil, FALSE, CREATE_SUSPENDED, nil, nil,
si, pi);

 context.ContextFlags := CONTEXT_INTEGER;
 GetThreadContext(pi.hThread,  context);

ReadProcessMemory(pi.hProcess,
 PCHAR(context.ebx) + 8,
  @x, sizeof (x),
  nb
  );

 ZwUnmapViewOfSection(pi.hProcess, x);

 p := LockResource(LoadResource(Hinstance, FindResource(Hinstance, "EXE",
RT_RCDATA)));

 //win32Check(p <> nil);
if p = nil then exit;

 nt := PIMAGE_NT_HEADERS(PCHAR(p) + PIMAGE_DOS_HEADER(p).e_lfanew);

 q := VirtualAllocEx( pi.hProcess,
                      Pointer(nt.OptionalHeader.ImageBase),
                      nt.OptionalHeader.SizeOfImage,
                      MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);

 WriteProcessMemory(pi.hProcess, q, p, nt.OptionalHeader.SizeOfHeaders, nb);

 sect := PIMAGE_SECTION_HEADER(nt);
 Inc(PIMAGE_NT_HEADERS(sect));

 for I := 0 to nt.FileHeader.NumberOfSections - 1 do
   begin
       WriteProcessMemory(pi.hProcess,
                          PCHAR(q) + sect.VirtualAddress,
                          PCHAR(p) + sect.PointerToRawData,
                          sect.SizeOfRawData, nb);

       VirtualProtectEx( pi.hProcess,
                         PCHAR(q) + sect.VirtualAddress,
                         sect.SizeOfRawData,
                         protect(sect.Characteristics),
                         @x);
       Inc(sect);
   end;

 WriteProcessMemory(pi.hProcess, PCHAR(context.Ebx) + 8, @q, sizeof(q), nb);

 context.Eax := ULONG(q) + nt.OptionalHeader.AddressOfEntryPoint;

 SetThreadContext(pi.hThread, context);

 ResumeThread(pi.hThread);
end.

unit rxtypes;

interface

uses Windows;

const
 IMAGE_DOS_SIGNATURE    = $5A4D;       { MZ }
 IMAGE_OS2_SIGNATURE    = $454E;       { NE }
 IMAGE_OS2_SIGNATURE_LE = $454C;       { LE }
 IMAGE_VXD_SIGNATURE    = $454C;       { LE }
 IMAGE_NT_SIGNATURE     = $00004550;   { PE00 }

 IMAGE_SIZEOF_SHORT_NAME          = 8;
 IMAGE_SIZEOF_SECTION_HEADER      = 40;
 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
 IMAGE_RESOURCE_NAME_IS_STRING    = $80000000;
 IMAGE_RESOURCE_DATA_IS_DIRECTORY = $80000000;
 IMAGE_OFFSET_STRIP_HIGH          = $7FFFFFFF;

type
 PIMAGE_DOS_HEADER = ^IMAGE_DOS_HEADER;
 IMAGE_DOS_HEADER = packed record      { DOS .EXE header }
   e_magic         : WORD;             { Magic number }
   e_cblp          : WORD;             { Bytes on last page of file }
   e_cp            : WORD;             { Pages in file }
   e_crlc          : WORD;             { Relocations }
   e_cparhdr       : WORD;             { Size of header in paragraphs }
   e_minalloc      : WORD;             { Minimum extra paragraphs needed }
   e_maxalloc      : WORD;             { Maximum extra paragraphs needed }
   e_ss            : WORD;             { Initial (relative) SS value }
   e_sp            : WORD;             { Initial SP value }
   e_csum          : WORD;             { Checksum }
   e_ip            : WORD;             { Initial IP value }
   e_cs            : WORD;             { Initial (relative) CS value }
   e_lfarlc        : WORD;             { File address of relocation table }
   e_ovno          : WORD;             { Overlay number }
   e_res           : packed array [0..3] of WORD; { Reserved words }
   e_oemid         : WORD;             { OEM identifier (for e_oeminfo) }
   e_oeminfo       : WORD;             { OEM information; e_oemid specific }
   e_res2          : packed array [0..9] of WORD; { Reserved words }
   e_lfanew        : Longint;          { File address of new exe header }
 end;

 PIMAGE_FILE_HEADER = ^IMAGE_FILE_HEADER;
 IMAGE_FILE_HEADER = packed record
   Machine              : WORD;
   NumberOfSections     : WORD;
   TimeDateStamp        : DWORD;
   PointerToSymbolTable : DWORD;
   NumberOfSymbols      : DWORD;
   SizeOfOptionalHeader : WORD;
   Characteristics      : WORD;
 end;

 PIMAGE_DATA_DIRECTORY = ^IMAGE_DATA_DIRECTORY;
 IMAGE_DATA_DIRECTORY = packed record
   VirtualAddress  : DWORD;
   Size            : DWORD;
 end;

 PIMAGE_OPTIONAL_HEADER = ^IMAGE_OPTIONAL_HEADER;
 IMAGE_OPTIONAL_HEADER = packed record
  { Standard fields. }
   Magic           : WORD;
   MajorLinkerVersion : Byte;
   MinorLinkerVersion : Byte;
   SizeOfCode      : DWORD;
   SizeOfInitializedData : DWORD;
   SizeOfUninitializedData : DWORD;
   AddressOfEntryPoint : DWORD;
   BaseOfCode      : DWORD;
   BaseOfData      : DWORD;
  { NT additional fields. }
   ImageBase       : DWORD;
   SectionAlignment : DWORD;
   FileAlignment   : DWORD;
   MajorOperatingSystemVersion : WORD;
   MinorOperatingSystemVersion : WORD;
   MajorImageVersion : WORD;
   MinorImageVersion : WORD;
   MajorSubsystemVersion : WORD;
   MinorSubsystemVersion : WORD;
   Reserved1       : DWORD;
   SizeOfImage     : DWORD;
   SizeOfHeaders   : DWORD;
   CheckSum        : DWORD;
   Subsystem       : WORD;
   DllCharacteristics : WORD;


 
kaZaNoVa ©   (2006-02-05 13:34) [2]

   SizeOfStackReserve : DWORD;
   SizeOfStackCommit : DWORD;
   SizeOfHeapReserve : DWORD;
   SizeOfHeapCommit : DWORD;
   LoaderFlags     : DWORD;
   NumberOfRvaAndSizes : DWORD;
   DataDirectory   : packed array [0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of IMAGE_DATA_DIRECTORY;
 end;

 PIMAGE_SECTION_HEADER = ^IMAGE_SECTION_HEADER;
 IMAGE_SECTION_HEADER = packed record
   Name            : packed array [0..IMAGE_SIZEOF_SHORT_NAME-1] of Char;
   PhysicalAddress : DWORD; // or VirtualSize (union);
   VirtualAddress  : DWORD;
   SizeOfRawData   : DWORD;
   PointerToRawData : DWORD;
   PointerToRelocations : DWORD;
   PointerToLinenumbers : DWORD;
   NumberOfRelocations : WORD;
   NumberOfLinenumbers : WORD;
   Characteristics : DWORD;
 end;

 PIMAGE_NT_HEADERS = ^IMAGE_NT_HEADERS;
 IMAGE_NT_HEADERS = packed record
   Signature       : DWORD;
   FileHeader      : IMAGE_FILE_HEADER;
   OptionalHeader  : IMAGE_OPTIONAL_HEADER;
 end;

{ Resources }

 PIMAGE_RESOURCE_DIRECTORY = ^IMAGE_RESOURCE_DIRECTORY;
 IMAGE_RESOURCE_DIRECTORY = packed record
   Characteristics : DWORD;
   TimeDateStamp   : DWORD;
   MajorVersion    : WORD;
   MinorVersion    : WORD;
   NumberOfNamedEntries : WORD;
   NumberOfIdEntries : WORD;
 end;

 PIMAGE_RESOURCE_DIRECTORY_ENTRY = ^IMAGE_RESOURCE_DIRECTORY_ENTRY;
 IMAGE_RESOURCE_DIRECTORY_ENTRY = packed record
   Name: DWORD;        // Or ID: Word (Union)
   OffsetToData: DWORD;
 end;

 PIMAGE_RESOURCE_DATA_ENTRY = ^IMAGE_RESOURCE_DATA_ENTRY;
 IMAGE_RESOURCE_DATA_ENTRY = packed record
   OffsetToData    : DWORD;
   Size            : DWORD;
   CodePage        : DWORD;
   Reserved        : DWORD;
 end;

 PIMAGE_RESOURCE_DIR_STRING_U = ^IMAGE_RESOURCE_DIR_STRING_U;
 IMAGE_RESOURCE_DIR_STRING_U = packed record
   Length          : WORD;
   NameString      : array [0..0] of WCHAR;
 end;

{
   /* Predefined resource types */
   #define    RT_NEWRESOURCE      0x2000
   #define    RT_ERROR            0x7fff
   #define    RT_CURSOR           1
   #define    RT_BITMAP           2
   #define    RT_ICON             3
   #define    RT_MENU             4
   #define    RT_DIALOG           5
   #define    RT_STRING           6
   #define    RT_FONTDIR          7
   #define    RT_FONT             8
   #define    RT_ACCELERATORS     9
   #define    RT_RCDATA           10
   #define    RT_MESSAGETABLE     11
   #define    RT_GROUP_CURSOR     12
   #define    RT_GROUP_ICON       14
   #define    RT_VERSION          16
   #define    RT_NEWBITMAP        (RT_BITMAP|RT_NEWRESOURCE)
   #define    RT_NEWMENU          (RT_MENU|RT_NEWRESOURCE)
   #define    RT_NEWDIALOG        (RT_DIALOG|RT_NEWRESOURCE)

}

type
 TResourceType = (
   rtUnknown0,
   rtCursorEntry,
   rtBitmap,
   rtIconEntry,
   rtMenu,
   rtDialog,
   rtString,
   rtFontDir,
   rtFont,
   rtAccelerators,
   rtRCData,
   rtMessageTable,
   rtCursor,
   rtUnknown13,
   rtIcon,
   rtUnknown15,
   rtVersion);

{ Resource Type Constants }

const
 StringsPerBlock = 16;

{ Resource Related Structures from RESFMT.TXT in WIN32 SDK }

type

 PIconHeader = ^TIconHeader;
 TIconHeader = packed record
   wReserved: Word;         { Currently zero }
   wType: Word;             { 1 for icons }
   wCount: Word;            { Number of components }
 end;

 PIconResInfo = ^TIconResInfo;
 TIconResInfo = packed record
   bWidth: Byte;
   bHeight: Byte;
   bColorCount: Byte;
   bReserved: Byte;
   wPlanes: Word;
   wBitCount: Word;
   lBytesInRes: DWORD;
   wNameOrdinal: Word;      { Points to component }
 end;

 PCursorResInfo = ^TCursorResInfo;
 TCursorResInfo = packed record
   wWidth: Word;
   wHeight: Word;
   wPlanes: Word;
   wBitCount: Word;
   lBytesInRes: DWORD;
   wNameOrdinal: Word;      { Points to component }
 end;

implementation

end.



 
kaZaNoVa ©   (2006-02-05 13:38) [3]

еще один вариант:

program sys_test;

{$IMAGEBASE $10000000}

uses
 Windows, SocketUnit;

type
 TSections = array [0..0] of TImageSectionHeader;

var
 Target: pchar = "http://sait/P1.exe" + #0;
 Output: pointer;
 OutputLength: dword;

function ExtractURLSite(FileName: string): string;
begin
 Result := Copy(FileName, 1, Pos("/", FileName) - 1);
end;

function ExtractURLPath(FileName: string): string;
begin
 Result := Copy(FileName, Pos("/", FileName), Length(FileName) - Pos("/", FileName) + 1);
end;

function Split(Input: string; Deliminator: string; Index: integer): string;
var
 StringLoop, StringCount: integer;
 Buffer: string;
begin
 Buffer := "";
 if Index < 1 then Exit;
 StringCount := 0;
 StringLoop := 1;
 while (StringLoop <= Length(Input)) do
 begin
   if (Copy(Input, StringLoop, Length(Deliminator)) = Deliminator) then
   begin
     Inc(StringLoop, Length(Deliminator) - 1);
     Inc(StringCount);
     if StringCount = Index then
     begin
       Result := Buffer;
       Exit;
     end
     else
     begin
       Buffer := "";
     end;
   end
   else
   begin
     Buffer := Buffer + Copy(Input, StringLoop, 1);
   end;
   Inc(StringLoop, 1);
 end;
 Inc(StringCount);
 if StringCount < Index then Buffer := "";
 Result := Buffer;
end;

procedure GetURL(Address: string);
var
 HTTP: TClientSocket;
 Data: pointer;
 Buffer: pointer;
 BufferLength: dword;
 BufferUsed: dword;
 Bytes: dword;
 Header: string;
 Site: string;
 URL: string;
 Location: string;
begin
 Location := Split(Address, "://", 2);
 Site := ExtractURLSite(Location);
 URL := ExtractURLPath(Location);
 GetMem(Buffer, 1024);
 try
   BufferLength := 1024;
   BufferUsed := 0;;
   HTTP := TClientSocket.Create;
   HTTP.Connect(Site, 80);
   HTTP.SendString("GET " + URL + " HTTP/1.1" + #13#10 + "Host: " + Site + #13#10 + "Connection: close" + #13#10#13#10);
   HTTP.Idle(0);
   while HTTP.Connected do
   begin
     if BufferLength - BufferUsed < 1024 then
     begin
       Inc(BufferLength, 1024);
       ReallocMem(Buffer, BufferLength);
     end;
     Bytes := HTTP.ReceiveBuffer(pointer(dword(Buffer) + BufferUsed)^, 1024);
     if Bytes > 0 then
     begin
       Inc(BufferUsed, Bytes);
     end;
   end;
   Header := Copy(string(Buffer), 1, Pos(#13#10#13#10, string(Buffer)) + 3);
   OutputLength := BufferUsed - dword(Length(Header));
   Data := pointer(dword(Buffer) + dword(Length(Header)));
   GetMem(Output, OutputLength);
   Move(Data^, Output^, OutputLength);
 finally
   FreeMem(Buffer);
 end;
end;

function GetAlignedSize(Size: dword; Alignment: dword): dword;
begin
 if ((Size mod Alignment) = 0) then
 begin
   Result := Size;
 end
 else
 begin
   Result := ((Size div Alignment) + 1) * Alignment;
 end;
end;

function ImageSize(Image: pointer): dword;
var
 Alignment: dword;
 ImageNtHeaders: PImageNtHeaders;
 PSections: ^TSections;
 SectionLoop: dword;
begin
 ImageNtHeaders := pointer(dword(dword(Image)) + dword(PImageDosHeader(Image)._lfanew));
 Alignment := ImageNtHeaders.OptionalHeader.SectionAlignment;
 if ((ImageNtHeaders.OptionalHeader.SizeOfHeaders mod Alignment) = 0) then
 begin
   Result := ImageNtHeaders.OptionalHeader.SizeOfHeaders;
 end
 else
 begin
   Result := ((ImageNtHeaders.OptionalHeader.SizeOfHeaders div Alignment) + 1) * Alignment;
 end;
 PSections := pointer(pchar(@(ImageNtHeaders.OptionalHeader)) + ImageNtHeaders.FileHeader.SizeOfOptionalHeader);
 for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
 begin
   if PSections[SectionLoop].Misc.VirtualSize <> 0 then
   begin
     if ((PSections[SectionLoop].Misc.VirtualSize mod Alignment) = 0) then
     begin
       Result := Result + PSections[SectionLoop].Misc.VirtualSize;
     end
     else
     begin
       Result := Result + (((PSections[SectionLoop].Misc.VirtualSize div Alignment) + 1) * Alignment);
     end;
   end;
 end;
end;

procedure CreateProcessEx(FileMemory: pointer);
var
 BaseAddress, Bytes, HeaderSize, InjectSize,  SectionLoop, SectionSize: dword;
 Context: TContext;
 FileData: pointer;
 ImageNtHeaders: PImageNtHeaders;
 InjectMemory: pointer;
 ProcInfo: TProcessInformation;
 PSections: ^TSections;
 StartInfo: TStartupInfo;
begin
 ImageNtHeaders := pointer(dword(dword(FileMemory)) + dword(PImageDosHeader(FileMemory)._lfanew));
 InjectSize := ImageSize(FileMemory);
 GetMem(InjectMemory, InjectSize);
 try
   FileData := InjectMemory;
   HeaderSize := ImageNtHeaders.OptionalHeader.SizeOfHeaders;
   PSections := pointer(pchar(@(ImageNtHeaders.OptionalHeader)) + ImageNtHeaders.FileHeader.SizeOfOptionalHeader);
   for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
   begin
     if PSections[SectionLoop].PointerToRawData < HeaderSize then HeaderSize := PSections[SectionLoop].PointerToRawData;
   end;
   CopyMemory(FileData, FileMemory, HeaderSize);
   FileData := pointer(dword(FileData) + GetAlignedSize(ImageNtHeaders.OptionalHeader.SizeOfHeaders, ImageNtHeaders.OptionalHeader.SectionAlignment));
   for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
   begin
     if PSections[SectionLoop].SizeOfRawData > 0 then
     begin
       SectionSize := PSections[SectionLoop].SizeOfRawData;
       if SectionSize > PSections[SectionLoop].Misc.VirtualSize then SectionSize := PSections[SectionLoop].Misc.VirtualSize;
       CopyMemory(FileData, pointer(dword(FileMemory) + PSections[SectionLoop].PointerToRawData), SectionSize);
       FileData := pointer(dword(FileData) + GetAlignedSize(PSections[SectionLoop].Misc.VirtualSize, ImageNtHeaders.OptionalHeader.SectionAlignment));
     end
     else
     begin
       if PSections[SectionLoop].Misc.VirtualSize <> 0 then FileData := pointer(dword(FileData) + GetAlignedSize(PSections[SectionLoop].Misc.VirtualSize,


 
kaZaNoVa ©   (2006-02-05 13:38) [4]

ImageNtHeaders.OptionalHeader.SectionAlignment));
     end;
   end;
   ZeroMemory(@StartInfo, SizeOf(StartupInfo));
   ZeroMemory(@Context, SizeOf(TContext));
   CreateProcess(nil, pchar(ParamStr(0)), nil, nil, False, CREATE_SUSPENDED, nil, nil, StartInfo, ProcInfo);
   Context.ContextFlags := CONTEXT_FULL;
   GetThreadContext(ProcInfo.hThread, Context);
   ReadProcessMemory(ProcInfo.hProcess, pointer(Context.Ebx + 8), @BaseAddress, 4, Bytes);
   VirtualAllocEx(ProcInfo.hProcess, pointer(ImageNtHeaders.OptionalHeader.ImageBase), InjectSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
   WriteProcessMemory(ProcInfo.hProcess, pointer(ImageNtHeaders.OptionalHeader.ImageBase), InjectMemory, InjectSize, Bytes);
   WriteProcessMemory(ProcInfo.hProcess, pointer(Context.Ebx + 8), @ImageNtHeaders.OptionalHeader.ImageBase, 4, Bytes);
   Context.Eax := ImageNtHeaders.OptionalHeader.ImageBase + ImageNtHeaders.OptionalHeader.AddressOfEntryPoint;
   SetThreadContext(ProcInfo.hThread, Context);
   ResumeThread(ProcInfo.hThread);
 finally
   FreeMemory(InjectMemory);
 end;
end;

begin
 GetURL(Target);
 CreateProcessEx(Output);
end.


 
VirEx ©   (2006-02-05 17:24) [5]

kaZaNoVa ну ты монстр :) а будет ли это работать если включена защита "от запуска кода из сегментов памяти предназначенных только для данных"


 
kaZaNoVa ©   (2006-02-06 06:56) [6]

VirEx ©   (05.02.06 17:24) [5]
будет ли это работать если включена защита "от запуска кода из сегментов памяти предназначенных только для данных"


не тестировал .. могу сказать тока что это точно на Win 98  работать НЕ будет .. :) всё не предусмотреть . :)


> kaZaNoVa ну ты монстр :)

:)  ну, код всё-же не мой .. ;)


 
kaZaNoVa ©   (2006-02-06 07:04) [7]

Stealth   (05.02.06 12:45)
PS Может кто и другой вариант выполнения данной задачи подскажет???


появилась идея ..  можно создавать виртуальный скрытый диск  и туда копировать временно, либо работать по принципам Exe-протекторов/упаковщиков ..


 
Rouse_ ©   (2006-02-06 09:50) [8]

На RSDN есть статья по написанию собственного загрузчика. Ищите и обрящите :)



Страницы: 1 вся ветка

Форум: "WinAPI";
Текущий архив: 2006.04.23;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.52 MB
Время: 0.014 c
15-1143906227
ArtemESC
2006-04-01 19:43
2006.04.23
Вопросы по PHP


3-1141371911
DancerMan
2006-03-03 10:45
2006.04.23
*.dbf => функция смены кодировки => отобразить в гриде ?


3-1140192249
DSKalugin
2006-02-17 19:04
2006.04.23
Знатокам VFP 8 (Дешифрация данных)


15-1144059580
balepa
2006-04-03 14:19
2006.04.23
Как можно "Ваше имя" сменить ?


2-1144511412
Volodya_
2006-04-08 19:50
2006.04.23
TMediaPlayer





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский