Перейти к содержанию

OllyDbg. Обнаружение устройства DirectX


aliast

Рекомендуемые сообщения

Статья. Нахождение указателей на устройство на примере игры Soldat

Да, понаделали они там глобальных изменений (это я про хостера своего бесплатного) и всё поудалялось... потом если найду статью перезалью. А пока вот статья про поиск Device pointers на примере игры Soldat. Что такое Device pointer я толком не понял, что-то связанное с DirectX программированием и созданием графических читов (wallhack - видеть сквозь стены и др.) путём перехвата DirectX функций. В общем для меня это всё тёмный лес, но статью понять можно. Осталось найти ей применение на практике =)

Скажу сразу, в игре Soldat имеется защита от читинга и под голым Olly запустить игру я не смог, пока не скачал с нашего сайта сборку shadow (скачать сборку) с кучей плагинов и игра сразу запустилась. Оригинал статьи

Нахождение указателей на устройство на примере игры Soldat

Итак, нам понадобится отладчик (OllyDbg – сборка shadow с набором плагинов) и DirectX8 игра (в данной статье речь пойдёт об игре Soldat). Можно применить всё нижеописанное и для DirectX9 игры, принцип тот же.

Открываем игру в OllyDbg (игра Soldat запакована, поэтому когда нас спросят о проведении анализа кода, отказываемся):

Soldat1.jpg

post-3-1297277560,62_thumb.jpg

Рис.1

Теперь запускаем игру кнопкой f9. Если у вас невзломанная версия игры, то перед запуском самой игры выскочит такое окошко:

Soldat2.jpg

post-3-1297277564,44_thumb.jpg

Рис.2

И вот после этого окошка можно приступать к поиску Direct3D8 интерфейса, для чего установите бряк на вызов функции Direct3DCreate8 (или 9 в случае с DirectX9 игрой). Бряк можно поставить, введя в Commandline "bp Direct3DCreate8" (без кавычек):

Soldat3.jpg

post-3-1297277568,17_thumb.jpg

Рис.3

Если у вас нет строки Commandline для ввода команд (обычно она располагается внизу под окном дампа), его можно вызвать через меню "Plugins->2 Command line->Command line" (где 2 может быть другой цифрой, у меня плагин был под номером 7).

Поставив бряк, нажмите кнопку «OK» («Готово» в русской версии) в окне игры с просьбой о регистрации.

Через 1-2 секунды сработает бряк внутри функции Direct3DCreate8 внутри библиотеки d3d8.dll (возможно, чтобы добраться до бряка понадобится пару раз нажать f9, только не перепрыгните наш бряк!):

Soldat4.jpg

post-3-1297277571,44_thumb.jpg

Рис.4

Чтобы найти место вызова функции, нажмём Ctrl+f9 и выполним всю функцию до инструкции «ret»:

Soldat5.jpg

post-3-1297277575,93_thumb.jpg

Рис.5

Теперь нажимаем f7 и возвращаемся в место вызова (вызов будет находиться строкой выше, на скриншоте вызов обозначен красной стрелкой):

Soldat6.jpg

post-3-1297277580,09_thumb.jpg

Рис.6

Чтобы найти функцию IDirect3D->CreateDevice, которая нам понадобится при поиске device указателя, я объясню, что за код идёт после вызова Direct3DCreate8.

Две инструкции mov вначале просто перемещают значения из регистра в регистр и нам это неинтересно. Первый call по всей видимости вызывает игровую функцию для проверки успешной инициализации Direct3D; это тоже нам не нужно. Инструкции cmp и je совершают прыжок, если D3D инциализировался с ошибкой. И это нам не нужно, но вот дальше смотрим внимательно. Инструкция "MOV EAX,DWORD PTR DS:[EBX]" идентична команде в с/с++: "eax = *ebx;". Она разыименовывает указатель. В нашем случае указатель на IDirect3D8 разыменовывается в ebx. Мы подбираемся всё ближе к нахождению createdivice. Следующая инструкция, "push eax", просто помещает eax в стек затем, чтобы потом командой "pop eax" легко восстановить eax, необходимый для вызова функции IDirect3D8. Далее мы видим "MOV EAX,DWORD PTR DS:[EAX]", разыменовывающая eax, т.к. ebx содержит указатель на указатель. Второй указатель – указатель на адресную таблицу функции IDirect3D8. Чтобы найти любую IDirect3D8 функцию со значением в eax, всё что нужно сделать, это выполнить "MOV EAX,DWORD PTR DS:[EAX+FUNCTIONINDEX]", где FUNCTIONINDEX – индекс IDirect3D8 функции в таблице. Далее, следующая и последняя инструкция, интересная для нас, это "CALL DWORD PTR DS:[EAX+8]", использующаяся вместо "MOV EAX,DWORD PTR DS:[EAX+FUNCTIONINDEX]", поскольку она не запоминает адрес функции, а просто напрямую её вызывает. Эту команду можно было бы заменить на "MOV EAX,DWORD PTR DS:[EAX+8] CALL EAX" и компилятор выполнил бы тоже самое. Теперь, когда вы узнали и поняли все эти вещи (я надеюсь) давайте наконец найдём CreateDevice.

Чтобы найти CreateDevice нужно заменить "CALL DWORD PTR DS:[EAX+8]" на "MOV EAX,DWORD PTR DS:[EAX+3C]". Мы делаем так, потому что индекс функции CreateDevice равен 3C в hex. Этой инструкцией мы добываем адрес CreateDevice и запоминаем его в EAX. Сделав так, нажимаем f7 пока Olly не выполнит изменённую инструкцию:

Soldat7.jpg

post-3-1297277584,67_thumb.jpg

Рис.7

На скриншоте видим в регистре EAX адрес функции CreateDevice, а модифицированная нами инструкция выделена красным прямоугольником.

Запоминаем адрес в EAX и перезапускаем игру, нажав Ctrl+f2.

Снова ставим бряк на Direct3DCreate8 и запускаем игру до бряка.

Находясь на сработавшем бряке, ставим бряк на запомненном адресе функции CreateDevice.

Снова запускаем игру (f9)

Нажимаем Ctrl+f9, затем f7 и выходим в место вызова CreateDevice.

Видим инструкцией выше "CALL DWORD PTR DS:[EAX+3c]":

Soldat8.jpg

post-3-1297277588,68_thumb.jpg

Рис.8

Это и есть то, что мы искали.

HRESULT CreateDevice(

UINT Adapter,

D3DDEVTYPE DeviceType,

HWND hFocusWindow,

DWORD behaviorFlags,

D3DPRESENT_PARAMETERS * pPresentationParameters,

IDirect3DDevice8 ** ppReturnedDeviceInterface

);

Последний аргумент и есть указатель на указатель устройства.

Чтобы найти их все, нужно вновь перезапустить игру (Ctrl+f2), поставить бряк на Direct3DCreate8. После срабатывания бряка, поставить бряк на "CALL DWORD PTR DS:[EAX+3c]". После срабатывания бряка нажать f7 и в стеке выше слов "Pointer to next SEH record" и будет записан указатель на указатель устройства:

Soldat9.jpg

post-3-1297277593,08_thumb.jpg

Рис.9

Чтобы найти указатель на устройство (не указатель на указатель), просто воспользуйтесь оператором разыменования в c/c++.

Что же мы нашли – адрес, в котором игра постоянно запоминает указатель на устройство. Теперь вы можете просто прочитать это значение, проверить больше ли оно нуля, и если да, сделать hook на D3D Device функцию, к примеру, EndScene.

Текстовый дамп из olly:


DISASSEMBLY
-----------

0049CA0B 50 PUSH EAX
0049CA0C 68 FC005D00 PUSH Soldat.005D00FC
0049CA11 6A 20 PUSH 20
0049CA13 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
0049CA16 50 PUSH EAX
0049CA17 57 PUSH EDI
0049CA18 56 PUSH ESI
0049CA19 A1 FC925C00 MOV EAX,DWORD PTR DS:[5C92FC]
0049CA1E 50 PUSH EAX
0049CA1F 8B00 MOV EAX,DWORD PTR DS:[EAX]
0049CA21 FF50 3C CALL DWORD PTR DS:[EAX+3C] ; d3d8.6D9C9BD0(CreateDevice)


STACK AT 0049CA1F(CreateDevice Argument List)
-----------------

0012E8EC 00000000 \ (Adapter)
0012E8F0 00000001 | (DeviceType)
0012E8F4 0011038E | (hFocusWindow)
0012E8F8 00000020 | (BehaviorFlags)
0012E8FC 005D00FC Soldat.005D00FC | (pPresentationParameters)
0012E900 005C9300 Soldat.005C9300 / (ppReturnedDeviceInterface)***
Code:

Надеюсь, эта статья хоть как-то вам поможет, а если вы ни разу не слышали про игру Soldat – Google вам в помощь.

Спасибо mre521 за эту статью

Перевод на русский: aliast

От MasterGH (10.02.2011)

Aliast, спасибо за статью. Я залил скриншоты на хост форума. Т.к. было бы очень жаль если бы какие-то скриншоты пропали со временем. Моё пожелание, заливать картинки на хостинг форума - нашего форума. У тебя есть эта возможность. Это просто пожелание, чтобы твой труд был более увековечен.Также советую всем и всегда хорошие статьи и инструменты копировать себе на хард, а то мало ли у нас рухнет хостинг. Хотя надеюсь что этого никогда не произойдёт иначе это будет траур.

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

Нормальная статья. Её поймут только те, кто делали хуки на COM бъекты.

Я правда не совсем понял зачем автор ставит бряки больше одного раза, когда можно было обойтись одним. Хотя может я и ошибаюсь, т.к. на память не помню описания DirectX-интерфейсов и инициализацию устройства... Если будет время, то я почитаю повнимательнее эту статью. А вообще автор статьи неправильно поступил описывая подробности как и что делать в OllyDbg без рисования сначала общей картины как он собирается вытаскивать указатель и из какой представляемой "логики кода" на коде С++ он собирается вытащить, и вообще какова логика кода стандартной инициализации устройства, и по каким смещения каких интерфейсов находятся методы возвращающие те или иные объекты... Будет время я прокомментирую эту статью нормально с примерами.

Ссылка на комментарий
Поделиться на другие сайты

По прежнему пока нет времени. Пока нашёл самый простой исходник по инициализации устройства.


// Файл: CreateDevice.cpp
//
// Описание: Это первый туториал по использованию Direct3D. В этом туториале всё что
// мы делаем - это создаём Direct3D устройство и используем его для очистки
// окна.
//
// Copyright (c) Micro$oft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#include <d3d9.h>




//-----------------------------------------------------------------------------
// Глобальные переменные
//-----------------------------------------------------------------------------
LPDIRECT3D9 g_pD3D = NULL; // Используется для создания D3D устройства
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Наше устройство рендеринга




//-----------------------------------------------------------------------------
// Имя: InitD3D()
// Описание: Инициализация Direct3D
//-----------------------------------------------------------------------------
HRESULT InitD3D( HWND hWnd )
{
// Создаём D3D объект, который необходим для создания D3D устройства.
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}

// Если устройство создано - возвращаем S_OK

return S_OK;
}




//-----------------------------------------------------------------------------
// Имя: Cleanup()
// Описание: Освобождаем все ранее инициализируемые объекты
//-----------------------------------------------------------------------------
VOID Cleanup()
{
if( g_pd3dDevice != NULL)
g_pd3dDevice->Release();

if( g_pD3D != NULL)
g_pD3D->Release();
}




//-----------------------------------------------------------------------------
// Имя: Render()
// Описание: Рисование сцены
//-----------------------------------------------------------------------------
VOID Render()
{
if( NULL == g_pd3dDevice )
return;

// Очистка backbuffer'а в голубой цвет
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

// начало сцены
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
// Рендеринг объектов сцены должен находиться здесь

// Конец сцены
g_pd3dDevice->EndScene();
}

// Выводим содержимое backbuffer'a на экран
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}




//-----------------------------------------------------------------------------
// Имя: MsgProc()
// Описание: Обработка сообщений
//-----------------------------------------------------------------------------
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;

case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
}

return DefWindowProc( hWnd, msg, wParam, lParam );
}




//-----------------------------------------------------------------------------
// Имя: WinMain()
// Описание: Точка входа приложения
//-----------------------------------------------------------------------------
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
// Регистрируем класс окна
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
"D3D Tutorial", NULL };
RegisterClassEx( &wc );

// Создание окна программы
HWND hWnd = CreateWindow( "D3D Tutorial", "D3D Tutorial 01: CreateDevice",
WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
GetDesktopWindow(), NULL, wc.hInstance, NULL );

// Инициализация Direct3D
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
// Показываем окно
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );

// Ввод петли сообщений
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}

UnregisterClass( "D3D Tutorial", wc.hInstance );
return 0;
}

//-----------------------------------------------------------------------------

Статья "Hooking a DirectX/COM Interface" позволит мне позже вспомнить детали и сравнить со статьё про хукинг в OllyDbg.

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

Ссылка на комментарий
Поделиться на другие сайты

  • 5 месяцев спустя...

Вот пример меню для трейнера на C++ правда это чит для мультиплеера, но всё же оболочка присутствует:


LPD3DXFONT m_pFont = NULL;
D3DVIEWPORT9 ScreenViewport;
..............................

//=======================================:[ Active Vars ]:=================================

//-------------------------:[Visuals]:-----------------------
//-----------------------
bool *WallHack;
bool *Lambert;
//-----------------------
bool *WpLambert;
//-----------------------
bool *ColoredPlayers;
int *ClPl_ColorNumb;
int *ClPl_ColorMode;
.......
etc
.......


//=====================================


FrmMain = new MainMenu(m_pD3Ddev);


int xx = (ScreenViewport.Width / 2) - 243;
int yy = (ScreenViewport.Height / 2) - 170;

FrmMain->CreateWND(NULL,xx,yy,44,true,MAKEINTRESOURCE(100),0);

FrmMain->frWnd[0]->Controls->AddTabControl(18, 8, 452, 267);

//===========================================================:[VISUALS]:===========================================

FrmMain->frWnd[0]->Controls->TabControls[0]->AddTab("Visuals");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddScrollBar(432,0,FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->scrly,200,&FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->scrly);

/* Волхак */
int y = 19;
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Wallhack:",11,y,100,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Lambert (Static): ",230,y+27,300,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(360,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
WallHack = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[0]->checked;
Lambert = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[1]->checked;
y += 29+22;

/* Weapoon Lambert */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Wpn Lambert (Static):",11,y,300,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
WpLambert = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[2]->checked;
y += 29+22;

/*Colored Players*/
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Colored Players:",11,y,300,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Color:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddColorBox(120, y+29, 35, 17, 4);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Mode: ",230,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddComboBox(290,y+29,80,17);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ComboBoxes[0]->AddLine("Textures");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ComboBoxes[0]->AddLine("Shaders");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Enable:",48,y+51,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(120,y+53,MAKEINTRESOURCE(60),0xFFA9E005);
ColoredPlayers = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[3]->checked;
ClPl_ColorNumb = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ColorBoxes[0]->value;
ClPl_ColorMode = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ComboBoxes[0]->value;
y += 53+22;

/*Chams*/
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Chams:",11,y,300,20,0xFFFFFFFF,0xFFFFFFFF); //+23
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("HColor:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f); //+23
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddColorBox(120, y+29, 35, 17, 4);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("IColor:",48,y+51,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddColorBox(120, y+53, 35, 17, 1);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Mode: ",230,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddComboBox(290,y+29,80,17);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ComboBoxes[1]->AddLine("Textures");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ComboBoxes[1]->AddLine("Shaders");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Enable:",230,y+51,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(290,y+53,MAKEINTRESOURCE(60),0xFFA9E005);
Chams = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[4]->checked;
Chams_ColorNumb1 = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ColorBoxes[1]->value;
Chams_ColorNumb2 = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ColorBoxes[2]->value;
Chams_ColorMode = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ComboBoxes[1]->value;
y += 53+22;

/*xHair*/
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("XHair:",11,y,300,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Color:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddColorBox(120, y+29, 35, 17, 4);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Enable:",230,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(290,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
CrossHair = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[5]->checked;
CrH_ColorNumb = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->ColorBoxes[3]->value;
y += 29+22;

/* WhiteWalls */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("WhiteWalls:",11,y,100,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
WhiteWalls = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[6]->checked;
y += 29+22;

/* AsusWallHack */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("AsusWallHack: ",11,y,200,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
AsusWallHack = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[0]->Controls->CheckBoxes[7]->checked;
y += 29+22;

//===========================================================:[REMOVALS]:===========================================

FrmMain->frWnd[0]->Controls->TabControls[0]->AddTab("Removals");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddScrollBar(432,0,FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->scrly,180,&FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->scrly);
y = 19;

/* NoFog */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("No Fog(Static):",11,y,200,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
NoFog = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->CheckBoxes[0]->checked;
y += 29+22;

/* NoSky */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("NoSky:",11,y,300,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Color:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddColorBox(120, y+29, 35, 17, 9);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Mode: ",230,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddComboBox(290,y+29,80,17);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->ComboBoxes[0]->AddLine("Textures");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->ComboBoxes[0]->AddLine("Shaders");
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Enable:",48,y+51,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddCheckBox(120,y+53,MAKEINTRESOURCE(60),0xFFA9E005);
NoSky = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->CheckBoxes[1]->checked;
Sky_ColorNumb = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->ColorBoxes[0]->value;
Sky_ColorMode = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->ComboBoxes[0]->value;

y += 53+22;

/* NoGrass */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("No Grass:",11,y,200,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
NoGrass = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->CheckBoxes[2]->checked;
y += 29+22;

/* NoSun */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("No Sun:",11,y,200,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
NoSun = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->CheckBoxes[3]->checked;
y += 29+22;

/* NoShadows */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("No Shadows:",11,y,200,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
NoShadows = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->CheckBoxes[4]->checked;
y += 29+22;

/* No Spread */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("No Spread:",11,y,200,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
NoSpread = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->CheckBoxes[5]->checked;
y += 29+22;

/* No Recoil */
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("No Recoil:",11,y,200,20,0xFFFFFFFF,0xFFFFFFFF);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddSeparator(11,y+20,409,1,0x99AAAAAA);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddTextArea("Enable:",48,y+27,100,20,0xFF6f6f6f,0xFF6f6f6f);
FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->AddCheckBox(120,y+29,MAKEINTRESOURCE(60),0xFFA9E005);
NoRecoil = &FrmMain->frWnd[0]->Controls->TabControls[0]->TabWindows[1]->Controls->CheckBoxes[6]->checked;
y += 29+22;
///
MainMenu *FrmMain; 

Не забываем про это:

m_pFont->OnLostDevice(); FrmMain->OnLostDevice(); 

И конец:

and in endscene:

post-2842-1312178759,88_thumb.jpg

post-2842-1312178771,81_thumb.jpg

post-2842-1312178782,03_thumb.jpg

Ссылка на комментарий
Поделиться на другие сайты

  • 5 месяцев спустя...

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

Вот мой скриншот:

85b3f9ed1c7bt.jpg

Вот это у меня не получается:

На скриншоте видим в регистре EAX адрес функции CreateDevice, а модифицированная нами инструкция выделена красным прямоугольником.

Запоминаем адрес в EAX и перезапускаем игру, нажав Ctrl+f2.

Снова ставим бряк на Direct3DCreate8 и запускаем игру до бряка.

Находясь на сработавшем бряке, ставим бряк на запомненном адресе функции CreateDevice.

Снова запускаем игру (f9)

Нажимаем Ctrl+f9, затем f7 и выходим в место вызова CreateDevice.

Видим инструкцией выше "CALL DWORD PTR DS:[EAX+3c]":

У меня адрес EAX 00382C00

Я так понял после нажатия Ctrl+f2, надо делать заново атач.

После того как я сделал атач, снимаю с паузы игру и ставлю бряк на Direct3DCreate8

На находящемся бряке поставил еще 1 бряк адрес (EAX)

Нажал F9

Нажал Ctrl+f9, затем f7 и вышел в меню вызова CreateDevice.

Вот скриншот:

2424730c0625t.jpg

Подскажите, правильно я сделал...

Ссылка на комментарий
Поделиться на другие сайты

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

Я просто прокомментирую.

Давайте вообще поймём, что в статье делается.Статья очень большая, а написать-то Автору первоисточника надо было чуть проще и статью читать с конца к началу.

Наша задача найти указатель на устройство редеринга g_pd3dDevice.Т.е. g_pd3dDevice это адрес по которому располагается адрес устройства.


LPDIRECT3D9 g_pD3D = NULL; // Используется для создания D3D устройства
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Наше устройство рендеринга

HRESULT InitD3D( HWND hWnd )
{
// Создаём D3D объект, который необходим для созданияD3D устройства.
if( NULL == ( g_pD3D =Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

if( FAILED( g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}


// Если устройство создано - возвращаем S_OK
return S_OK;
}

Посмотрите последний абзац статьи, там где есть скриншот стека с выделенным последним адресом - т.е. g_pd3dDevice. Наша задача как в статье выйти на вызвов g_pD3D->CreateDevice(….,&g_pd3dDevice). Чтобы выйти на этот участок, мы должны найти указательна g_pD3D, прозвонить call [ [eax] + 3c], где eax= g_pD3D. Вот там где мы окажемся по call и будет вызов g_pD3D->CreateDevice(….,&g_pd3dDevice) и там мы снимем показания стека на бряке. Чтобы найти g_pD3D мы должны найти Direct3DCreate9 или иную смотря какая у васверсия дайректИкс dll-и.Теперь смотрим внимательно статью ещё раз и думаем как снять показания, когда и какие бряки ставить.

Но я предлагаю совершенно другой вариант. В том же ollyDbg поставить условный бряк на функцию процедуры окна на сообщение WM_PAINT и искать машинный код вот такого условия сравнения с нулём

if( NULL ==g_pd3dDevice )

return;

Где после этого сравнения будут вызовы типа call [[g_pd3dDevice]+ смещение] или g_pd3dDevice->функция(..):


//-----------------------------------------------------------------------------
// Имя: Render()
// Описание: Рисование сцены
//-----------------------------------------------------------------------------
VOIDRender()
{
if( NULL == g_pd3dDevice )
return;

// Очистка backbuffer'а в голубой цвет
g_pd3dDevice->Clear( 0, NULL,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

// начало сцены
if( SUCCEEDED(g_pd3dDevice->BeginScene() ) )
{
//Рендеринг объектов сцены должен находиться здесь

// Конец сцены
g_pd3dDevice->EndScene();
}

// Выводим содержимое backbuffer'a на экран
g_pd3dDevice->Present( NULL, NULL, NULL,NULL );
}

//-----------------------------------------------------------------------------
// Имя: MsgProc()
// Описание: Обработка сообщений
//-----------------------------------------------------------------------------
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;

case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
}

return DefWindowProc( hWnd, msg, wParam, lParam );
}

Для проверки g_pd3dDevice посмотрите смещения call [[g_pd3dDevice]+ смещение] где смещение равно

вызову функции Clear(), вызову BeginScene(),вызову EndScene(),Present(). Если подобную манипуляцию сделать однажды, то потом установить адрес устройства редеринга будет проще чем через способ , который автор первоисточника написал в статье.   Но он-то написал это на примере работы в отладчике в режиме отладки, а если его способ автоматизировать то его способ будет даже наверно попроще моего, т.к. мой способ автоматизировать будет сложнее.

Ссылка на комментарий
Поделиться на другие сайты

Мм, продолжу тему. В конце статьи пишется про возможность хука ендсцены, etc. Имеем адрес устройсва, найденного заранее для игры на dx9. Пробую сделать хук:

LPDIRECT3DDEVICE9	myGame;
while(!myGame)
myGame = (LPDIRECT3DDEVICE9)0x059457F8;// наш адрес д3д устройства

//насколько я знаю EndScene находится под индексом 42 в виртуальной таблице
//тогда можно использовать кодкейв, ничего лучше ms detours для этого нету ; )

oEndScene=(tEndScene)DetourFunction((PBYTE)GameDevice[42],(PBYTE)&EndScene);

...

но нифига не работает :grin:

есть у кого более глубокие знания?

Ссылка на комментарий
Поделиться на другие сайты

Если почитать DXSDK внимательно, то указатель там двухуровневый. То есть, не указатель на устройство, а указатель на указатель на устройство.

Ссылка на комментарий
Поделиться на другие сайты

Мм, продолжу тему. В конце статьи пишется про возможность хука ендсцены, etc. Имеем адрес устройсва, найденного заранее для игры на dx9. Пробую сделать хук:

LPDIRECT3DDEVICE9	myGame;
while(!myGame)
myGame = (LPDIRECT3DDEVICE9)0x059457F8;// наш адрес д3д устройства

//насколько я знаю EndScene находится под индексом 42 в виртуальной таблице
DWORD* GameDevice = (DWORD*)myGame;
GameDevice = (DWORD*)GameDevice[0];
//тогда можно использовать кодкейв, ничего лучше ms detours для этого нету ; )

oEndScene=(tEndScene)DetourFunction((PBYTE)GameDevice[42],(PBYTE)&EndScene);

...

но нифига не работает :grin:

есть у кого более глубокие знания?

Берёшь отладчик, берёшь справку, берёшь компилятор + DirectX SDK. И "получаешь более глубокие знания" ;)  

Ссылка на комментарий
Поделиться на другие сайты

Есть, всё получилось, благо существует MSDN :lol: я ещё малость попарился с OnLostDevice, но оказывается там всё просто, Reset имеет индекс 16


HRESULT WINAPI Reset( LPDIRECT3DDEVICE9 D3D_DEVICE, D3DPRESENT_PARAMETERS* pPresentationParameters )
{
if(mFont){mFont->OnLostDevice();}

HRESULT MY_RESULT = oReset( D3D_DEVICE, pPresentationParameters );

if(MY_RESULT==D3D_OK) {if(mFont){mFont->OnResetDevice();}}

return MY_RESULT;
}

4132afbf4a25e4a058710cc2a55feb0f.jpg

теперь меню трейнера можно делать в самой игре :grin:

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

Я абсолютно не сомневался, что у тебя получится задуманная идея.

Мне как бы похвастаться по работе с голым ДаректИКС нечем, кроме компиляций и разбора с десяток примеров как простых так и примеров из DirectX SDK. Такой хук аналогичный твоему я тоже делал, только на Дельфи. Но могу посоветовать кое-что для дальнейшего развития и исследований.

1) Посмотреть жизненный цикл пары программ из примеров из DirectX SDK. Таких моментов как потеря устройства, directX обработка ввода с клавиатуры и мышки, почитать какие события можно обработать, какие общие функции. Это может тебя заинтересовать и толкнуть на новые идеи, которые могут быть тебе будет интересны.

2) Попробовать написать свое приложение с ДаректИКС в котором обрабатывается directX ввод с клавиатуры или с мышки. Обычно это вереница кейсов в свитчах. Это тоже толкает на некоторые интересные идеи по поиску в играх в отладчике нужного участка кода по вводу от польозвателя

3) Если никогда не делал игровые сцены, то очень полезно на игровом движке посмотреть примеры игр. Советую Unity3D и пример AngryBots который идёт в комплекте. Обратить внимание на управление персонажем, управление камерой, на построение иерархии игровых объектов. Но а также советую пример сцены 3D - шутера:

3rdpersonshooter-logo-reflected.jpg

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

Ссылка на комментарий
Поделиться на другие сайты

  • 3 недели спустя...

Порылся ещё глубже, теперь осталось научится получать координаты моделек и можно сделать универсальный аимбот на все игры :rolleyes:

b9f107d45c7652e95be8c90a62fa7764.jpg

Выложу таблицу с вершинами и примитивами для команды контртеррористов на последнюю версию:


Numvertices & Primcounts

GIGN
3304 5003
2196 3061
1404 1919
811 989
466 550
68 40
68 34

Seal Team
3887 4974
2487 3006
73 59
899 910
513 451
369 279

SAS
3417 5030
2245 2998
1498 1822
929 1007
499 533
409 433

GSG 9
3206 4872
2130 3004
1424 1858
814 945
457 510
87 63
343 344

штук 5-6 я правда пропустил при поисках, т.к. чтобы на отходить на 1м и выискивать заново нужно иметь совсем железные нервы :-D

Ссылка на комментарий
Поделиться на другие сайты

  • 1 месяц спустя...

DX11 это нечто, надо же было стереть работу с рисование текста, пришлось юзать стороннюю библиотеку. Чтож, посылаем туда же Dx11:

5eedc1a76c84225a7ef495ec3bb61913.png

теперь у меня есть коллекция хуков dx начиная с .. dx 8

ps. по-моему кроме меня и US никому эта тема не интересна :))

Ссылка на комментарий
Поделиться на другие сайты

DX11 это нечто, надо же было стереть работу с рисование текста, пришлось юзать стороннюю библиотеку. Чтож, посылаем туда же Dx11:

5eedc1a76c84225a7ef495ec3bb61913.png

теперь у меня есть коллекция хуков dx начиная с .. dx 8

ps. по-моему кроме меня и US никому эта тема не интересна :))

У меня пока что руки не дошли. :lol:

Ссылка на комментарий
Поделиться на другие сайты

Уже четыре дня пробую и наконец получилось,спасибо Synapsе-у за объяснения. :-D

0ded3f3d407e2d8a9bf8c394ca03487c.jpg

Прикольно у тебя вышло, у меня по уроку в теме, не выходит хукать ендсцену в мв3, можно какую-нибудь отдельную статью именно на эту игру ?

В современных играх как-то не так :)

Ссылка на комментарий
Поделиться на другие сайты

  • 9 месяцев спустя...

Статья. Нахождение указателей на устройство на примере игры Soldat

Да, понаделали они там глобальных изменений (это я про хостера своего бесплатного) и всё поудалялось... потом если найду статью перезалью. А пока вот статья про поиск Device pointers на примере игры Soldat. Что такое Device pointer я толком не понял, что-то связанное с DirectX программированием и созданием графических читов (wallhack - видеть сквозь стены и др.) путём перехвата DirectX функций. В общем для меня это всё тёмный лес, но статью понять можно. Осталось найти ей применение на практике =)

Скажу сразу, в игре Soldat имеется защита от читинга и под голым Olly запустить игру я не смог, пока не скачал с нашего сайта сборку shadow (скачать сборку) с кучей плагинов и игра сразу запустилась. Оригинал статьи

Ребята могу как нибудь скачать сборку? А то говорит что нет прав у меня.

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.