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

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

Я пытаюсь получить значение EDX в test.myEDX который в основном всегда ставит неправильное значение EDX в test.myEDXно часть этого EDX значение кажется правильным, что очень странно, некоторые проблемы с битами Hi / Lo, я полагаю.

Позвольте мне объяснить, что я пытаюсь сделать.
Сначала я перехватываю место в памяти, которое содержит произвольный ассемблерный код.

Я перехватываю его, устанавливая аппаратную точку останова в этом месте, и тогда я могу видеть все значения регистров в этом месте во времени.
Теперь я создал простую программу, в которой каждый раз, когда процессор переходит в точку останова, я помещаю EDX значение в структуру в C ++.
Насколько я знаю, ничто не должно изменить EDX зарегистрироваться, пока я помещаю его в структуру, если только значения в структуре не изменят EDX что я проверял не так, я проверял, перемещая EDX сначала в EAX затем прохождение EAX в структуру, которая должна иметь то же значение, что и EDX и это все еще может быть неправильно EAX используется также при помещении данных в структуру? Тогда я не пошел дальше, когда тестировал все регистры, чтобы найти, какой из них не используется, сомневаюсь, что это даже проблема в любом случае.

Еще одна вещь, которую нужно учитывать, чтобы на самом деле делать какие-либо операции, такие как положить EDX в структуру я должен сделать в C ++ текущий EIP перейдя к моей голой функции, я знаю, что ранее занимаясь этим делом, голые функции вообще не изменяют никаких регистров, они просто используются как способ расширения текущего кода ассемблера в этом EIP в намного больший ассемблерный код без мусора, который C ++ добавил бы при входе в подпрограммы.

Я также использую PUSHAD а также PUSHFD восстановить ранее установленные значения регистра после завершения сброса EDX в структуре это все в безопасной среде (пока я не использую PUSH/POPнеправильно конечно) прежде чем позвонить POPFD затем POPAD

Я почти не знаю ASM, но, глядя на множество примеров, я никогда не видел, чтобы регистры перемещались таким образом. (что, очевидно, не имело бы смысла дублировать регистр, но даже один и тот же регистр переместился на 2 разных адреса один за другим, который я не видел).

MOV EBX, EDX
MOV ECX, EDX

Но на самом деле я вижу что-то подобное (что, как мне показалось, было моей проблемой, почему это не сработало), (это не так, я все еще не понимаю, что делаю неправильно)

MOV EBX, EDX
MOV EAX, EDX //Theory: in order to move EDX a second time into ECX, I must not move EDX directly into ECX.
MOV ECX, EAX

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

Я думаю, что любой из этих вариантов AL BL регистры облажались EDX или, может быть TEST или же JE Я не могу сказать, я вставил этот код в OllyDBG и, похоже, ничего не изменилось EDX почему загадка EDX неправильно, может быть адрес обновляется слишком медленно? поскольку он основан на скорости ОЗУ, которая не синхронизируется со скоростью процессора (конечно, все глупые теории).

В любом случае, это все, что я могу объяснить, здесь код.

struct TestStruct {
int myEDX;
int mySetEDX;
} test;

extern bool optionOne = false;
extern bool optionTwo = false;
DWORD gotoDumpBackAddress = 0x40012345;

void __declspec( naked ) dump(void) {
__asm {
PUSHAD
PUSHFD
XOR EAX, EAX //zero EAX, used below as AL (optionOne)
XOR EBX, EBX //zero EBX, used below as BL (optionTwo)
MOV AL, optionOne //optionOne set
MOV BL, optionTwo //optionTwo set

TEST EAX, EAX //Tests if optionOne equals == 0, then je will be equal.
je gotoOptionOne //Jumps if optionOne equals 0.
TEST EBX, EBX //Tests if optionTwo equals == 0, then je will be equal.
je gotoOptionTwo  //Jumps if optionTwo equals 0.

gotoOptionOne:
//This the default case (improper value in EDX..., I could just use address at [ESI+0x2] which is old EDX, which is risky since it's delayed (outdated)
MOV DWORD PTR DS:[ESI+0x2], EDX //(normal operation)
MOV test.myEDX, EDX //Stores freshest EDX to test.myEDX (wrong EDX value)
JMP finish  //Clear temporary used registers and go back to next asm code

//Special case. (works mostly properly)
//Thing is EDX gets updated very frequently, Causes no side-effect only because
//[ESI+0x2] gets updated in a different location as well a bit later to renew the value.
//So it's not even noticeable, but when I run this at it's peak speeds, you start to see the flickering effect, which isn't normal, if I run peak speeds without hook.
//I eliminated the problem that the hook could cause the flicker effect since after
//I call a empty naked Hook with just return address to same location and disable hook
//Then re-enable hook and repeat step above (no flicker occurs).
gotoOptionTwo:
//MOV DWORD PTR DS:[ESI+0x2], EDX //(normal operation), omitted
MOV EAX, DWORD PTR DS:[ESI+0x2] //Old EDX, but atleast it's correct.
MOV test.myEDX, EAX //Stores old EDX into struct test.myEDX
MOV EAX, test.mySetEDX //Replace old EDX with what I wish it should be.
MOV DWORD PTR DS:[ESI+0x2], EAX //nySetEDX into what EDX should of did.
JMP finish //Clear temporary used registers and go back to next asm code
finish:
POPFD
POPAD

JMP gotoDumpBackAddress //return to starting location before dump + 1.

}
}

РЕДАКТИРОВАТЬ: Хорошо, я не объяснил, как я тестирую этот код.

Я не собираюсь объяснять, как я делаю аппаратную точку останова, я не хочу, чтобы этот метод был общедоступным в Интернете, из-за моих соображений безопасности в будущем.
Но это работает, вызывая другую DLL, которая связывается с системным драйвером.

Но это должно объяснить, как я это проверяю.

Я запускаю в другом потоке в этой DLL инъекции.

void diffThread() {
while(1) {
if(GetAsyncKeyState(VK_NUMPAD0)) {
optionOne != optionOne;
Sleep(1000);
}

if(GetAsyncKeyState(VK_NUMPAD1)) {
optionTwo != optionTwo;
Sleep(1000);
}

Sleep(10);
}
}

bool __stdcall DllMain(HINSTANCE hInst,DWORD uReason,void* lpReserved)
{
if(uReason == DLL_PROCESS_ATTACH)
{
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&diffThread,0 ,NULL,NULL);
}
else if(uReason == DLL_PROCESS_DETACH)
{
ExitThread(0);
}
return true;
}

2

Решение

Хорошо, честно говоря, это неправильно на многих уровнях, я имею в виду сам код.
То, что вы пытаетесь сделать, — это пробиться на территорию C, сказав компилятору отступить, и вы делаете свое дело, написав от руки код. Иногда это может пригодиться, но вы должны иметь в виду, что вы также теряете некоторые функции C, особенно те вещи, которые выполняются для вас автоматически, и, насколько я знаю, компиляция Microsoft просто не любит, когда вы возитесь в inline-ассемблере (не получите я ошибаюсь, компилятор MASM великолепен, но он не очень хорошо сочетается с компилятором C)

// doing this is not the best since the compiler may
// do some nasty stuff like aligning the struct that
// you have to take watch out for in asm
struct TestStruct {
int myEDX;
int mySetEDX;
} test;

extern bool optionOne = false;
extern bool optionTwo = false;
DWORD gotoDumpBackAddress = 0x40012345; // static memory address? this should fail like instantly, I dont really know what this is supposed to be

void __declspec( naked ) dump(void) {
__asm {
PUSHAD // you shouldn't normally push all registers only the ones you actually used!
PUSHFD
XOR EAX, EAX // this is how you zero out but honestly you can use EAX's low and high parts as 2 16bit regsiters instead of using 2 32bits for 2 bits of data
XOR EBX, EBX
MOV AL, optionOne //optionOne set
MOV BL, optionTwo //optionTwo set

TEST EAX, EAX // This check is meaning less because if gotoOptionTwo is false you move on regardless of gotoOpeionOne's value
je gotoOptionOne
TEST EBX, EBX // This test should always be true isn't it?
je gotoOptionTwo

gotoOptionOne:
// Assuming that ESI is coming from the system as you said that there is a
// breakpoint that invokes this code, do you even have access to that part of the memory?
MOV DWORD PTR DS:[ESI+0x2], EDX
MOV test.myEDX, EDX // this is just a C idom 'struct' this doesnt really exist in ASM
JMP finish

gotoOptionTwo:
MOV EAX, DWORD PTR DS:[ESI+0x2]
MOV test.myEDX, EAX
MOV EAX, test.mySetEDX
MOV DWORD PTR DS:[ESI+0x2], EAX
JMP finish
finish:
POPFD
POPAD

JMP gotoDumpBackAddress //return to starting location before dump + 1.

}
}

Итак, чтобы ответить на ваш вопрос,

Я думаю, что самая большая проблема здесь — это линия

MOV test.myEDX, EDX

Как правило, в ASM нет такой вещи, как область действия, например, вы говорите ей получить значение myEDX из теста, что означает получение указателя на значение теста и затем получение значения по указателю. Это может работать, но это зависит от компилятора C, чтобы помочь вам.

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

LEA ecx,test // get the address of test
MOV DWORD PTR[ecx],edx // use that address to move the data into the struct

Теперь вы говорите ему поместить эти данные в структуру, однако это делает некоторые предположения, например, здесь мы знаем, что первый элемент — это тот, в который мы хотим поместить данные (myEDX), и мы предполагаем, что DWORD такой же, как int в C (в Windows это обычно так), но опять же предположения плохие!

1

Другие решения

Некоторые вещи, которые я заметил в вашем коде:

Прежде всего, я не думаю, что вам действительно нужно делать pushad а также pushfd Вы действительно используете только один регистр, потому что ebx даже не понадобится, и eax компилятор не считает сохраняемым в любом случае.

    XOR EAX, EAX //zero EAX, used below as AL (optionOne)
MOV AL, optionOne //optionOne set
TEST EAX, EAX //Tests if optionOne equals == 0, then je will be equal.
je gotoOptionOne //Jumps if optionOne equals 0.

MOV AL, optionTwo //optionTwo set
TEST EAX, EAX //Tests if optionTwo equals == 0, then je will be equal.
je gotoOptionTwo  //Jumps if optionTwo equals 0.

Ваше предположение

 MOV EBX, EDX
MOV EAX, EDX //Theory: in order to move EDX a second time into ECX, I must not move EDX directly into ECX.
MOV ECX, EAX

это просто неправильно. Вы можете перемещать один и тот же регистр так часто, как хотите, ограничений нет. Единственное что МОЖЕТ БЫТЬ Следует учитывать, что вы ищете максимальную производительность из-за планирования команд, но это совершенно другая проблема, и она все равно здесь не применима.

Так просто делать

 MOV EBX, EDX
MOV ECX, EDX

было бы хорошо.

Ты используешь ESI но это нигде не инициализировано. Возможно, это правильно, так как я не совсем понимаю, что должен делать ваш код.

Вы используете переменную test Вот

MOV test.myEDX, EAX
MOV EAX, test.mySetEDX

но эта переменная даже не объявлена, так откуда компилятору знать, откуда она? А для использования его в качестве смещения вам нужно будет использовать регистр в качестве базового адреса.

1

По вопросам рекламы [email protected]