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

как подменить значение регистра?


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

В байт-коде игры есть инструкция mov eax, [ecx]. Так вот в регистре ecx содержится адрес, как его подменить? я пишу программку на C++ и надо через неё подменить этот адрес.

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

Устанавливаешь брйкпоинт на этот кусок кода.

Когда брейпоинт срабатывает, вызываешь GetThreadContext, подменяешь нужный тебе регистр, вызываешь SetThreadContext.

Введи в поиске "аппаратный перехват", найдёшь мой топик с видеоуроком, там будет всё что тебе нужно, только малость код адаптировать придется.

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

Устанавливаешь брйкпоинт на этот кусок кода.

Когда брейпоинт срабатывает, вызываешь GetThreadContext, подменяешь нужный тебе регистр, вызываешь SetThreadContext.

Введи в поиске "аппаратный перехват", найдёшь мой топик с видеоуроком, там будет всё что тебе нужно, только малость код адаптировать придется.

Так я уже нашёл кусок кода. Есть адрес, как именно в этом месте подменить регистр? Ведь на каждом байте меняются регистры.

Можно пример как с адреса считать регистр, а затем переписать его?

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

/*----------------------------------------------------------------------------*/

 

Можно, конечно, написать про использование inline-ассемблера. А-ля:

 

DWORD GetMyData()

{

    DWORD result = 0;

    __asm mov result,ecx

    return result;

}

 

Но, я так понимаю, тебе надо сделать именно инжект кода. Регистры меняются не

"каждый байт", а тогда, когда их меняют, за исключением всяких там esp и прочих

ebp. Скажем, ecx не поменяется, пока его не поменяют. Алгоритм такой, я думаю:

 

0. Выделить где-нибудь 4 байта под переменную, хранящую значение.

1. Заменить нужный кусок кода инструкцией вида "JMP 0x01234567", где 0x01234567

- адрес процедуры вроде GetMyData выше. Как заменить? JMP в опкодах - 0xE9,

дальше нужно посмотреть, какой адрес больше - процедуры GetMyData или куска

кода, который меняем. Из большего вычитаем меньшее (хотя вроде можно и

наоборот), так как прыжки на адрес всегда делаются относительно текущей позиции

- типа на 100 байт вперёд или на 100 байт назад.

2. В процедуре делаем это самое "mov result,ecx" и выходим.

3. Используем полученный result.

 

В принципе, malloc(), virtualprotectex(), memcpy(), readprocessmemory() - вот и

всё необходимое, помимо получения pID процесса (но тут уже варианты на твой

вкус).

 

PS: Таки getthreadcontext() будет всё равно попроще, я думаю.

 

/*----------------------------------------------------------------------------*/

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

В байт-коде игры есть инструкция mov eax, [ecx]. Так вот в регистре ecx содержится адрес, как его подменить? я пишу программку на C++ и надо через неё подменить этот адрес.

 

Сводится к замене одной цепочки байт на другую (или другие цепочки байт по адресам). Цепочки получаются через дизассемблер/ассемблер. Запись в память на С++, изменение и возвращение прав на запись у страницы памяти, открытие и поиск процесса. Все что достаточно знать. Подробности можно поискать в Интернете или примерах исходников трейнеров.

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

А зачем вообще права менять? права стоят "чтение, выполнение" но всё прекрасно пишется в память. Это я имею ввиду запись байт-кода. А значения не пишутся, я ставлю полный доступ и не возвращаю исходное состояние.

DWORD GetMyData()

{

DWORD result = 0;

__asm mov result,ecx

return result;

}

^ это интересная штука. но как ей показать адрес с которого нужно считать ecx?

да, я сделаю инжект кода. я вот думал сделать так:

записать прямо в игре на пустой байт-код "00000000" своё значение например "500" и заменить инструкцию с "mov eax, [ecx]" на "mov eax, [game+offset]" но возникла проблема. Каждый запуск точка входа игры меняется и соответственно адрес game+offset тоже. Я то могу получить в dword этот адрес и смещение, но как его перевести в byte? или как записать dword в память. но ещё надо как-то "перевернуть" байты.

поэтому я подумал сложно это. может проще просто подменить ecx.

расскажите про getthreadcontext как им осуществить задуманное?

а про выделение памяти тоже думал. тогда я бы просто прыгнул туда. но если моя программа закроется то память тоже, и не будет работать. А если не освобождать память, то это же тоже плохо. Но это ладно, я хотел через malloc сделать, но не понял как.

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

/*----------------------------------------------------------------------------*/

 


int foo = 0;

while(!_kbhit()) {

    __asm {

        push eax

        mov eax,[foo]

        inc eax

        mov [foo],eax

        pop eax

    }

    printf("foo address: 0x%X foo value: %d\n", &foo, foo);

    Sleep(1000);

}


 

Попробуй этот примерчик в отладчике поковырять, лучше - в OllyDbg каком-нибудь

или том же Cheat Engine. Может, наведёт на мысли.

 

/*----------------------------------------------------------------------------*/

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

ну тут ты увеличил foo на 1 и переписал его. но &foo что делает? указатель на адрес содержажий это значение в памяти на данный момент? которая существует пока открыта программа.

я не понимаю как в конкретном адресе это сделать. инструкции ведь хранятся по определенным адресам и выполняются в определенный момент.

просто тут в этом примере значение храниться у тебя в программе. а у меня в дпугой. да ещё и в регистре.

просто если я напишу

mov [foo],eax

то регистр будет из моей программы, а надо из чужой.

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

/*----------------------------------------------------------------------------*/

 

Верно, обращение по амперсанду выдаёт адрес переменной в памяти, обращение по

имени - значение переменной. Точно так же, если к тебе пришёл адрес переменной,

а ты хочешь получить значение из этого адреса - делаешь звёздочку:

 

int foo = 0; // Объявляем переменную типа int с именем foo

int* pFoo = &foo; // Объявляем указатель на int с именем pFoo, кладём в него

// указатель на foo

int bar = *pFoo; // Объявляем переменную типа int с именем bar и кладём в него

//значение указателя pFoo

 

В результате bar будет равно 0, то есть изначальному значению foo. То же самое,

что и:

 

int foo = 0;

int bar = foo;

 

Приведу ещё раз ассемблерную вставку из своего примера:

 

__asm {

        push eax
        mov eax,[foo]
        inc eax
        mov [foo],eax
        pop eax
    }
 
0. Сохраняем регистр EAX в стек - мало ли, что там было до нашей функции. Вдруг
важное?
1. Кладём в EAX значение (не адрес!) переменной foo
2. Увеличиваем eax на 1
3. Кладём в переменную foo новое значение
 
То же самое, что и:
 
foo++;
или:
foo += 1;
или:
foo = foo + 1;
 

Дальше мы сообщаем в консоль значение foo и адрес foo в памяти. Если посмотреть

этот код в отладчике, то увидим мы нечто такое:

 

CPU Disasm
Address   Hex dump          Command                                                     
00023C0F  |.  50            |PUSH EAX
00023C10  |.  A1 5C810200   |MOV EAX,DWORD PTR DS:[foo]
00023C15  |.  40            |INC EAX
00023C16  |.  A3 5C810200   |MOV DWORD PTR DS:[foo],EAX
00023C1B  |.  58            |POP EAX
 
Наша задача - как-то впихнуть в это место код, который скопирует EAX в
какую-нибудь переменную, т.е. в какой-то другой адрес памяти. Вроде этого:
 
CPU Disasm
Address   Hex dump          Command                                                     
00023C0F  |.  50            |PUSH EAX
00023C10  |.  A1 5C810200   |MOV EAX,DWORD PTR DS:[foo]
00023C15  |.  40            |INC EAX
00023C16  |.  A3 5C810200   |MOV DWORD PTR DS:[foo],EAX
                             MOV [0x1234567],EAX
00023C1B  |.  58            |POP EAX
 
Но просто так взять и записать кусок кода посреди программы мы не можем. Почему?
Потому что компилятор - умный. И при компиляции весь код, переводя его в
машинные инструкции (колоночка hex dump) кладёт их поближе друг к другу, но что
самое важное - все переходы по коду (jmp, je, jne и так далее) осуществляются
относительно текущего адреса. Следовательно, если порядок байт нарушить, впихнув
лишних, то переходы, которые раньше ссылались на правильно заданные при
компиляции места, начнут ссылаться в какую-нибудь далёкую страну, например
SEG_FAULT. Приятный момент тут в том, что после компиляции в исполняемый файл
код всегда будет лежать по одним и тем же адресам, т.е. адрес инструкции "MOV
DWORD PTR DS:[foo],EAX" как был равен 0x00023C16, так там и останется.
 
Нужно как-то впихнуть сюда ещё 5 байт (столько команда MOV и занимает вместе с
операндами). Текущий код нельзя расширить, но можно поменять. Тут-то нам на
помощь и спешит команда JMP. Стираем текущую команду MOV, вместо неё пихаем JMP
на другой адрес, а там делаем так:
 
MOV DWORD PTR DS:[foo],EAX
MOV [0x1234567],EAX
RET
 
В итоге оригинальный код начинает выглядеть так:
 
CPU Disasm
Address   Hex dump          Command                                                     
00023C0F  |.  50            |PUSH EAX
00023C10  |.  A1 5C810200   |MOV EAX,DWORD PTR DS:[foo]
00023C15  |.  40            |INC EAX
00023C16  |.  A3 5C810200   |JMP 0x1234567
00023C1B  |.  58            |POP EAX
 
Как такое сделать? Вот пример:
d89cf30eef.png
0xCF3C62 - оригинальная инструкция программы.
00CF3C08 - JMP SHORT 00CF3C62 - безусловный переход на оригинальную инструкцию,
перемещённую в другое место.
 
Правда, нам ещё надо вернуться обратно, и команда RET нам в этом не поможет -
она прыгает на адрес, который лежит в стеке, а мы с ним ничего не делали.
Поэтому нужен ещё один переход:
 
Он на выделенной строчке. Теперь у нас есть место, куда ещё можно впихнуть
какие-то команды. Мы знаем адрес переменной bar, куда и хотели записать значение
регистра. Делаем:
838d402d87.png
 
MOV DWORD PTR DS:[bar],EAX - то же самое, что и просто MOV [CF8160],EAX. CF8160
- указатель на bar, т.е. её адрес в памяти. Скобки означают, что мы используем
значение по этому адресу, туда-то и кладём EAX. Примерно вот так инъекции кода в
Cheat Engine и работают, разве что память выделяется функцией, а не берётся в
текущем адресном пространстве программы (так делали чуток раньше, лет 5-10
назад).
 
В общем, адрес кода, который надо заменить на JMP, можно посмотреть в отладчике.
Там же можно посмотреть, какие байты записывать. И адрес переменной, куда будет
записываться результат, тоже можно узнать там. Записывать нужно через
WriteProcessMemory, выделить кусок памяти - через malloc, или же в Cheat Engine
найти адрес такого куска. В моём примере переходы заменены на SHORT JMP, потому
что прыгают недалеко (+\-127 байт), в нормальной ситуации это был бы такой
опкод (машинный код):
 
E9 AA BB CC DD
 
AABBCCDD - перевёрнутый (это важно!) адрес, куда нужно прыгать.
 
0. VirtualAllocEx (длина байткода копирования в переменную + 5 (длина JMP) +
длина байткода оригинальной инструкции)
2. WriteProcessMemory (адрес функции, "E9 перевёрнутый_адрес_для_прыжка"
3. WriteProcessMemory (адрес VirtualAllocEx, "байткод оригинальной инструкции"
3. WriteProcessMemory (адрес VirtualAllocEx + длина байткода оригинальной
инструкции, "байткод копирования в переменную")
4. WriteProcessMemory (адрес VirtualAllocEx + длина байткода копирования в
переменную + длина байткода оригинальной инструкции, "E9 на перевёрнутый
адрес функции + 5*")
 
*+5, потому что иначе мы прыгнем на начало нашего же JMP, а нам надо на
следующую инструкцию после него.

 

/*----------------------------------------------------------------------------*/

4577ac3d85.png
  • Плюс 2
Ссылка на комментарий
Поделиться на другие сайты

keng, спасибо за подробнейшее объяснение. А почему бы не писать в адресное пространство самой игры? Вот смотри. В игре есть огромный кусок байт-кода который содержит нули, тоесть 0x00000000. и такого пространства много. Почему бы не записать туда?

Но куда бы я не записал, как перевернуть адрес? Допустим я программно выделил память у меня есть адрес в DWORD памяти которую я выделил, как перевернуть эти байты?


а VirtualAllocEx тоже не простая штука. Ей ещё какие-то привилегии надо. Как их получить?

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

/*----------------------------------------------------------------------------*/

 

Дык можно и в него писать, я не спорю. Только нельзя писать, куда попало. Надо

или найти блок байт, который не используется (В отладчике Cheat Engine меню

Tools - Scan for code caves), или же выделить через VirtualAllocEx, для неё

нужно указать флажки, размер блока и pID процесса, который можно получить,

например, через OpenProcess (пример моего кода):

 

GetMemoryForCave proc bsize:dword

LOCAL phndl:dword

invoke    OpenProcess,PROCESS_ALL_ACCESS,NULL,pid
.if eax != 0
    mov       phndl,eax                
    invoke    VirtualAllocEx,phndl,NULL,bsize,MEM_COMMIT,PAGE_EXECUTE_READWRITE        
    push      eax
    invoke    CloseHandle,phndl
    pop       eax
.endif        
ret

GetMemoryForCave endp

 

Как переворачивать адрес? Ну, это массив. Руками! 

 

WriteBuffer proc t:dword,f:dword,b:dword,s:dword

    push eax
    push ebx
    mov  ebx,    
    mov  dword ptr [ebx],0E8h    
    mov  eax,t
    sub  eax,f
    sub  eax,5
    mov  byte ptr [ebx+1],al
    mov  byte ptr [ebx+2],ah
    shr  eax,16
    mov  byte ptr [ebx+3],al
    mov  byte ptr [ebx+4],ah    
    mov  eax,s
    sub  eax,5    
    cmp  eax,0        
    je   $+10            
    mov  byte ptr [ebx+5],090h
    inc  ebx
    dec  eax    
    jne  $-11    
    pop  ebx
    pop  eax
    ret
    
WriteBuffer endp

 

/*----------------------------------------------------------------------------*/

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

/*----------------------------------------------------------------------------*/

 

1. Да. И чаще всего - будет.

2. Руками - в смысле, алгоритм напиши. У тебя есть массив байт, тебе его надо

развернуть задом наперёд.

 

/*----------------------------------------------------------------------------*/

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

а почему регистр xmm* не принимает никаких цифр? только цифры из адресов movss xmm0, [*] почему нельзя так movss xmm0, 100500? < это бы сразу всё решило  :)

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

/*----------------------------------------------------------------------------*/

 

Ещё как принимает, но для него команды нужны специальные. Это один из регистров 

SSE - штуки-расширения процессора, которая позволяет работать со 128-битными

числами. Это расширение часто используется в играх, где чего-нибудь много,

например стратегиях - там много юнитов. Подробнее можно вот [тут] почитать.

 

/*----------------------------------------------------------------------------*/

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

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

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

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