20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
1

Создание потока в чужом процессе

30.05.2013, 13:26. Показов 3619. Ответов 26
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте.
У меня беда, полтергейст, не знаю даже что, 5-й день уже не могу ничего сделать, помогите пожалуйста, может кто на свежий взгляд определит в чем проблема.
В общем, в С++ Builder этот код работает, в делфи функция с асм кодом тоже работает, и только у меня на Visul C++ MFC процесс, в котором я создаю этот поток, вылетает. Вылетает как раз на строчке создания потока с GetLastError() = 299.
Проект простой, три функции плюс кнопка:

Функция определения ИД процесса:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DWORD PidbyName(CString nameofprogram)
{
    HANDLE pHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
    PROCESSENTRY32 ProcessEntry;
    ProcessEntry.dwSize = sizeof(ProcessEntry);
    BOOL Loop = Process32First(pHandle,&ProcessEntry);
    while(Loop)
    {
        if(strcmp(ProcessEntry.szExeFile, nameofprogram) == 0)
        {
            pidgmwnd = ProcessEntry.th32ProcessID;
            CloseHandle(pHandle);
            return pidgmwnd;
        }
        Loop = Process32Next(pHandle, &ProcessEntry);
    }
    return 0;
}
Функция, которая должна исполняться в потоке
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
void Target_THREAD()
{
    DWORD wid = 0;
    DWORD Myfunc = 0x00693D60;
    _asm
    {
        MOV EAX,DWORD PTR DS:[BA]                           //#define BA  0x00B8FBCC
        PUSH wid
        MOV ECX,DWORD PTR DS:[EAX+0x20]
        ADD ECX,0x0EC
        CALL Myfunc
    }
}
Инжектирующая функция

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
BYTE Inject(void* Func, void* Params)
{
    CString str;
    DWORD* pfunc = 0;
    DWORD* paramaddr = 0;
    DWORD* lpNumberOfBytes = NULL;
    HANDLE hProcThread;
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pidgmwnd);                 //pidgmwnd=PidbyName();
 
    pfunc = (DWORD*)VirtualAllocEx(hProcess,NULL,250,MEM_COMMIT,PAGE_READWRITE);
    WriteProcessMemory(hProcess,pfunc,Func,250,lpNumberOfBytes);
 
    paramaddr = (DWORD*)VirtualAllocEx(hProcess,NULL,250,MEM_COMMIT,PAGE_READWRITE);
    WriteProcessMemory(hProcess,paramaddr,Params,250,lpNumberOfBytes);
 
    
    hProcThread = CreateRemoteThread(hProcess,0,0,(LPTHREAD_START_ROUTINE)pfunc,paramaddr,0,0); 
    //DWORD t = GetLastError();
    //str.Format("%lu",t);
    //MessageBox(NULL,str,"",MB_OK|MB_ICONINFORMATION);
 
 
    WaitForSingleObject(hProcThread,INFINITE);
    CloseHandle(hProcThread);
    CloseHandle(hProcess);
    VirtualFreeEx(hProcess, paramaddr,0, MEM_RELEASE);
    VirtualFreeEx(hProcess, pfunc,0, MEM_RELEASE);
    return 1;
}
Ну, и собственно сам вызов функции, кнопка

C++
1
2
3
4
5
void CTestAssemblerDlg::OnBnClickedOk()
{
    Inject(&Target_THREAD,0);
    //OnOK();
}
Вот такая беда...
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
30.05.2013, 13:26
Ответы с готовыми решениями:

Как закрыть поток в чужом процессе зная адрес этого потока
Здравствуйте, я хочу закрыть поток в чужом процессе зная адрес потока (test.dll!test001+0x60520),...

Как закрыть поток в чужом процессе зная адрес этого потока
Здравствуйте, я хочу закрыть поток в чужом процессе зная адрес потока (test.dll!test001+0x60520),...

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

Изменить значения по заданному адресу в чужом процессе
почему не работает не меняет значения нужно менять значения по адресу 5E02C0 на 2 var buf :...

26
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 15:18  [ТС] 2
Весь проект:

VirusTotal
Вложения
Тип файла: rar TestAssembler.rar (37.6 Кб, 12 просмотров)
0
Ушел с форума
Эксперт С++
16458 / 7422 / 1186
Регистрация: 02.05.2013
Сообщений: 11,617
Записей в блоге: 1
30.05.2013, 18:03 3
Цитата Сообщение от Xantrax Посмотреть сообщение
C++
1
2
3
4
5
6
7
8
9
10
11
12
void Target_THREAD()
{
 DWORD wid = 0;
 DWORD Myfunc = 0x00693D60;
 _asm
 {
 MOV EAX,DWORD PTR DS:[BA] //#define BA 0x00B8FBCC
 PUSH wid
 MOV ECX,DWORD PTR DS:[EAX+0x20]
 ADD ECX,0x0EC
 CALL Myfunc
 }
}
0x00693D60, 0x00B8FBCC - это что за магические числа ? Откуда они взяты ?

Цитата Сообщение от Xantrax Посмотреть сообщение
C++
1
pfunc = (DWORD*)VirtualAllocEx(hProcess,NULL,250,MEM_COMMIT,PAGE_READWRITE);
Здесь должно быть PAGE_EXECUTE_READWRITE.
0
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 18:11  [ТС] 4
0x00693D60, 0x00B8FBCC - это что за магические числа ? Откуда они взяты ?
0x00B8FBCC - Базовый адрес программы, в данном случае игрового процесса (Perfect World от mail.ru).
0x00693D60 - адрес функции игрового процесса, которая выполняет выбор/сброс цели. Если ей передать параметр 0, то соответственно - это сброс цели.
А сама функция получена из дебагера(к примеру ollydbg).
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 18:26 5
Цитата Сообщение от Xantrax Посмотреть сообщение
0x00B8FBCC - Базовый адрес программы, в данном случае игрового процесса (Perfect World от mail.ru).
ImageBase имеете ввиду? Уверены что база постоянна. Функция может импортироватся из dll, а dll фиксапится. И вообще у вас код странный, что вы в ECX кладёте?
0
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 18:48  [ТС] 6
Я вам точно говорю, что базовый адрес программы статичен.
asm код получен мною из дебагера, если дословно, то ассемблерный код выглядит вот так:
Assembler
1
2
3
4
5
6
7
CPU Disasm
Address   Hex dump          Command                                  Comments
00477255  |> \A1 CCFBB800   MOV EAX,DWORD PTR DS:[0B8FBCC]
0047725A  |.  57            PUSH EDI                                 ; /Arg1
0047725B  |.  8B48 20       MOV ECX,DWORD PTR DS:[EAX+20]            ; |
0047725E  |.  81C1 EC000000 ADD ECX,0EC                              ; |
00477264  |.  E8 F7CA2100   CALL 00693D60                            ; \elementclient.00693D60
Добавлено через 14 минут
ImageBase имеете ввиду?
Не, знаю что вы имеете в виду, но базовый адрес - это статический адрес, от которого путем нахождения значений по смещениям можно найти параметры, в том числе и функцию, которая представлена выше...
P.S. у меня VS 2005 это как-то накладывает отпечаток на мои неудачи? Хотя я думаю - маловероятно. Я уже много чего попробовал, выбивает клиент и все. Хотя на Delfi и на Builder-е e у людей работает...
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 18:49 7
Xantrax, покажите в дебагере код который заинжектили.
0
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 18:54  [ТС] 8
Если после вылета клиента нажать отладка и посмотреть сообщение ошибки, то вот:
http://i.pixs.ru/thumbs/7/9/1/... 092791.jpg

Если сразу после вызова функции создания потока поставить GetLastError(), то ошибка 299 (как я уже и говорил)....
Т.е. получается, что он не может что-то считать, а вот что именно, без понятия...
Xantrax, покажите в дебагере код который заинжектили.
Я показал, вы именно это хотели увидеть?

И, да, я уже проверил свой компьютер на вирусы, и на всякий случай пронал тест оперативной памяти, сбойных блоков нет...
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 19:17 9
Цитата Сообщение от Xantrax Посмотреть сообщение
Я показал, вы именно это хотели увидеть?
Нет, я хотел увидеть сам код. Как его компиль откомпилил. Т.е. вам бы еще отладчиком приаттачится.
Вам ведь просто с нулём нужно функцию вызвать. Зачем лишнии команды? Попробуйте такой код:
Assembler
1
2
3
4
00401278 >/$ 6A 00          PUSH 0
0040127A  |. B8 603D6900    MOV EAX,693D60
0040127F  |. FFD0           CALL EAX
00401281  \. C3             RETN
Лутше байтами пропешите (не асм вставкой)
6A 00 B8 60 3D 69 00 FF D0 C3
0
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 19:52  [ТС] 10
Нет, я хотел увидеть сам код. Как его компиль откомпилил. Т.е. вам бы еще отладчиком приаттачится.
Тут, я не понял, к чему приаттачиваться. Поподробней опишите что делать-то надо? Аттачиться к Visual Studio?
Зачем лишнии команды? Попробуйте такой код:
Как мне выдал дебагер, так я и записал, отступление от ассемблера мне кажется - смерть (неминуемый крах клиента).

Ну, вот это компилятор естественно не понимает,
Assembler
1
MOV EAX,693D60
надо написать MOV EAX,0x00693D60. Испытал, результат тот же - крах...

Лутше байтами пропешите (не асм вставкой)
Видите ли что... Вот как раз отправка пакетов идет на ура и все получается, но мне нужно что бы именно асм код внедрился.
К сожалению такого же простенького проекта с отправкой пакетов у меня пока нет (чуть попозже сделаю и выложу), но приведу функцию инжекта которая отправляет пакеты и все исполняется...


Адреса, которые используются в функции отправки пакетов:
#define BA 0x00B8FBCC
#define F_SEND_PACKET 0x0069F9C0

Структура пакета:
C++
1
2
3
4
5
struct PACKET
{
  int len;  // длина
  BYTE Bytes[60];   // данные пакета
};
Здесь мы делаем все, что инжектируем(отправляем):
C++
1
2
3
4
5
6
7
8
struct INJECTOR
{
  DWORD pid;       // Идентификатор Процесса
  void* pFunction; // Указатель на память для функций
  void* pParams;   // Указатель на память для параметров
  BYTE SendPacket(PACKET*);
  void DeselectTarget();
}
Функция отправки пакетов:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
BYTE INJECTOR::SendPacket(PACKET* pack)
{
  HANDLE hProcThread;
  
  char fdata[30]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x49\x20\x68\x11\x11\x11\x11\x68\x22\x22\x22\x22\xB8\x33\x33\x33\x33\xFF\xD0\x61\xC3";
  int lenfunc = 30;
  DWORD func = F_SEND_PACKET;
  DWORD ba = BA;
  DWORD len = pack->len;
 
  HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,false,pid);  //pid - ИД процесса
  if (!hProcess) return 0;
 
  WriteProcessMemory(hProcess,pParams,pack->Bytes,len,NULL); // инжектим данные пакета, Packet pack; объявлен в конструкторе
 
  DWORD addr=DWORD(pParams); // возьмём адрес расположения данных нашего пакета
  memcpy(fdata+3,&ba,sizeof(DWORD));
  memcpy(fdata+11,&len,4);
  memcpy(fdata+16,&addr,4); // запишем адрес расположения пакета прямо в массив кода
  memcpy(fdata+21,&func,4);
 
  WriteProcessMemory(hProcess,pFunction,fdata,lenfunc,NULL); // инжектим наш код
 
  hProcThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)pFunction,NULL,NULL,NULL);
  if(hProcThread==INVALID_HANDLE_VALUE) // не удалось создать поток
  {
    CloseHandle(hProcess);
    return 0;
  }
 
  WaitForSingleObject(hProcThread, INFINITE); // ожидаем завершения работы потока
  CloseHandle(hProcThread); // освобождаем
  CloseHandle(hProcess);
  return 1;
}
Функция нахождения ИД процесса:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DWORD CNewBotDlg::PidbyName(CString nameofprogram)
{
    HANDLE pHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
    PROCESSENTRY32 ProcessEntry;
    ProcessEntry.dwSize = sizeof(ProcessEntry);
    BOOL Loop = Process32First(pHandle,&ProcessEntry);
    while(Loop)
    {
        if(strcmp(ProcessEntry.szExeFile, nameofprogram) == 0)
        {
            pid = ProcessEntry.th32ProcessID;
            CloseHandle(pHandle);
            return pid;
        }
        Loop = Process32Next(pHandle, &ProcessEntry);
    }
    return 0;
}
Ну, и собственно сама функция снятия таргета с цели:
C++
1
2
3
4
5
6
7
8
void INJECTOR::DeselectTarget()
{
    PACKET pack;
    pack.len = 2;
    char data[3] = "\x08\x00";   //08 00 
    memcpy(pack.Bytes,data,pack.len);
    SendPacket(&pack);
}
Еще раз повторю, что отправка пакетов проходит на ура!

Добавлено через 7 минут
Но, мне нужно осилить именно отправку ассемблерного кода, для более сложных действий в дальнейшем, потому что их проще сделать выудив ассемблерный код...
1
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 20:07 11
Цитата Сообщение от Xantrax Посмотреть сообщение
Тут, я не понял, к чему приаттачиваться.
К самой игре в которую сделали инжект. Поставте бесконечный цикл в начале асм вставки. Затем в Olly нажмите File->Attach и выберити процесс игры. Так же в программе которая инжектит, сделайте вывод адреса который возвращается от VirtualAllocEx. Чтобы знать по какому адресу код вписался (чтоб найти в Olly). Затем вы сможете выйти из цикла ПКМ -> New origin here * и отладить программу и понять причину падения.

Или проще сделайте. Откомпилируйте exe, с той вставкой, которая у вам была изначально. И залейте сюда. Чтоб я его в hex редакторе посмотрел, у меня просто студии не стоит.
0
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 20:16  [ТС] 12
Откомпилируйте exe, с той вставкой, которая у вам была изначально.
Т.е. просто скинуть сюда exe-шник?
Пожалуйста...
Спасибо вам, что помогаете, 5-й день уже мучаюсь....
Вложения
Тип файла: rar TestAssembler.rar (538.8 Кб, 7 просмотров)
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 20:37 13
Вот функция которую вы инжектите:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
004EF700   55                        PUSH EBP
004EF701   8BEC                      MOV EBP,ESP
004EF703   81EC D8000000             SUB ESP,0D8
004EF709   53                        PUSH EBX
004EF70A   56                        PUSH ESI
004EF70B   57                        PUSH EDI
004EF70C   8DBD 28FFFFFF             LEA EDI,DWORD PTR SS:[EBP-D8]
004EF712   B9 36000000               MOV ECX,36
004EF717   B8 CCCCCCCC               MOV EAX,CCCCCCCC
004EF71C   F3:AB                     REP STOS DWORD PTR ES:[EDI]
004EF71E   C745 F8 00000000          MOV DWORD PTR SS:[EBP-8],0
004EF725   C745 EC 603D6900          MOV DWORD PTR SS:[EBP-14],693D60
004EF72C   3E:A1 CCFBB800            MOV EAX,DWORD PTR DS:[B8FBCC]
004EF732   FF75 F8                   PUSH DWORD PTR SS:[EBP-8]
004EF735   3E:8B48 20                MOV ECX,DWORD PTR DS:[EAX+20]
004EF739   81C1 EC000000             ADD ECX,0EC
004EF73F   FF55 EC                   CALL DWORD PTR SS:[EBP-14]
004EF742   5F                        POP EDI
004EF743   5E                        POP ESI
004EF744   5B                        POP EBX
004EF745   81C4 D8000000             ADD ESP,0D8
004EF74B   3BEC                      CMP EBP,ESP
004EF74D   E8 1AF1FEFF               CALL 004DE86C                            ; TestAsse.004DE86C
004EF752   8BE5                      MOV ESP,EBP
004EF754   5D                        POP EBP
004EF755   C3                        RETN
Перед ретом компилятор ставит CALL 004DE86C, вот прога и падает, т.к. в контексте игры такой функции нету. В общем вам надо в виде байтов код инжектить.
1
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 20:51  [ТС] 14
Ммм, беда... Что ж получается, в VS нельзя инжектить асм код? Может другим компилятором каким пройтись?
Бред какой-то. Может более новая версия VS исправит эту огрешность компилятора??
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 20:56 15
Вот, попробуйте (только оформите правельно 0xA1,0xCC и т.д.):
A1 CC FB B8 00 6A 00 8B 48 20 81 C1 EC 00 00 00 B8 60 3D 69 00 FF D0 C3
Ну и передаёте функции VirtualAllocEx указатель на массив этих байт.
Миниатюры
Создание потока в чужом процессе  
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 21:01 16
Цитата Сообщение от Xantrax Посмотреть сообщение
Что ж получается, в VS нельзя инжектить асм код? Может другим компилятором каким пройтись?
Может в настройках чего-нибудь потыкать. Я не знаю. Вообще такие вещи лутше на нормальном асме писать (MASM32 допустим), ну и потом можно приленковывать как .obj файл к c++ проекту.
0
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 21:05  [ТС] 17
Массив сам по себе уже указатель, имя массива указывает на начало массива.
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 21:12 18
Цитата Сообщение от _lucius_ Посмотреть сообщение
Ну и передаёте функции VirtualAllocEx указатель на массив этих байт.
Я ошибся. Извеняюсь, я хотел сказать WriteProcessMemory.
Цитата Сообщение от Xantrax Посмотреть сообщение
Массив сам по себе уже указатель, имя массива указывает на начало массива.
Этот массив это код. Его и надо заинжектить и запустить. A1 CC FB B8 00 - первая инструкция, 6A 00 - вторая
В итоге вот что получается:
Assembler
1
2
3
4
5
6
7
004030F0   A1 CCFBB800      MOV EAX,DWORD PTR DS:[B8FBCC]
004030F5   6A 00            PUSH 0
004030F7   8B48 20          MOV ECX,DWORD PTR DS:[EAX+20]
004030FA   81C1 EC000000    ADD ECX,0EC
00403100   B8 603D6900      MOV EAX,693D60
00403105   FFD0             CALL EAX
00403107   C3               RETN
Код делает то что вам нужно, только переменных не использует.
0
20 / 20 / 2
Регистрация: 28.11.2012
Сообщений: 366
30.05.2013, 21:25  [ТС] 19
Так?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
BYTE Inject(void* Func, void* Params)
{
    char packet[25] = "\xA1\xCC\xFB\xB8\x00\x6A\x00\x8B\x48\x20\x81\xC1\xEC\x00\x00\x00\xB8\x60\x3D\x69\x00\xFF\xD0\xC3";
    CString str;
    DWORD* pfunc = 0;
    DWORD* paramaddr = 0;
    DWORD* lpNumberOfBytes = NULL;
    HANDLE hProcThread;
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pidgmwnd);
 
    pfunc = (DWORD*)VirtualAllocEx(hProcess,NULL,250,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess,pfunc,packet,250,lpNumberOfBytes);
 
    /*paramaddr = (DWORD*)VirtualAllocEx(hProcess,NULL,250,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess,paramaddr,Params,250,lpNumberOfBytes);*/
 
    
    hProcThread = CreateRemoteThread(hProcess,0,0,(LPTHREAD_START_ROUTINE)pfunc,paramaddr,0,0); 
    //DWORD t = GetLastError();
    //str.Format("%lu",t);
    //MessageBox(NULL,str,"",MB_OK|MB_ICONINFORMATION);
 
 
    WaitForSingleObject(hProcThread,INFINITE);
    CloseHandle(hProcThread);
    CloseHandle(hProcess);
    VirtualFreeEx(hProcess, paramaddr,0, MEM_RELEASE);
    VirtualFreeEx(hProcess, pfunc,0, MEM_RELEASE);
    return 1;
}
Хотя чего я спрашиваю? да, так! Все работает, но это же не асм код(((

WriteProcessMemory
Да, да, я вас понял, потому что VirtualAllocEx не принимает такие параметры))

Добавлено через 7 минут
Да, и в CreateRemoteThread - вместо paramaddr, написать NULL. Параметры нам все равно больше не нужны...
0
390 / 178 / 2
Регистрация: 14.03.2012
Сообщений: 443
30.05.2013, 21:27 20
Xantrax, ааа, кстати можно ведь поток не ретом завершить, а через API.
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
30.05.2013, 21:27
Помогаю со студенческими работами здесь

Установить защиту памяти в чужом процессе на запись
Делаю трейнер для игры. Мне нужно по определенному адресу памяти установить разрешение на запись.

Управление памятью в чужом процессе используя его апи
За пару дней с++ не со многими вещами разобрался, поэтому мог написать местами глупости. Суть:...

Directx рисование в чужом x64 процессе; Hex опкоды в ассемблер
Добрый вечер. Я замечал, что такие темы уже имеются на форуме и на других тоже, но все они сводятся...

Нужна помощь в получении base address DLL в чужом процессе.
Что то странное с форумом происходит, слово "помощь" вызывает какую-то ошибку :O_O: ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2023, CyberForum.ru