Сборка, как перевести JNE в код C без доступа к флагу ZF

Эмуляция ASM to C Code почти завершена... просто пытаюсь решить эти проблемы второго прохода.

Допустим, я получил эту функцию ASM

401040  MOV EAX,DWORD PTR [ESP+8]
401044  MOV EDX,DWORD PTR [ESP+4]
401048  PUSH ESI
401049  MOV ESI,ECX
40104B  MOV ECX,EAX
40104D  DEC EAX
40104E  TEST ECX,ECX
401050  JE 401083
401052  PUSH EBX
401053  PUSH EDI
401054  LEA EDI,[EAX+1]
401057  MOV AX,WORD PTR [ESI]
40105A  XOR EBX,EBX
40105C  MOV BL,BYTE PTR [EDX]
40105E  MOV ECX,EAX
401060  AND ECX,FFFF
401066  SHR ECX,8
401069  XOR ECX,EBX
40106B  XOR EBX,EBX
40106D  MOV BH,AL
40106F  MOV AX,WORD PTR [ECX*2+45F81C]
401077  XOR AX,BX
40107A  INC EDX
40107B  DEC EDI
40107C  MOV WORD PTR [ESI],AX
40107F  JNE 401057
401081  POP EDI
401082  POP EBX
401083  POP ESI
401084  RET 8

Моя программа создаст для него следующее.

int Func_401040() {
    regs.d.eax = *(unsigned int *)(regs.d.esp+0x00000008);
    regs.d.edx = *(unsigned int *)(regs.d.esp+0x00000004);
    regs.d.esp -= 4;
    *(unsigned int *)(regs.d.esp) = regs.d.esi;
    regs.d.esi = regs.d.ecx;
    regs.d.ecx = regs.d.eax;
    regs.d.eax--;
    if(regs.d.ecx == 0)
        goto label_401083;
    regs.d.esp -= 4;
    *(unsigned int *)(regs.d.esp) = regs.d.ebx;
    regs.d.esp -= 4;
    *(unsigned int *)(regs.d.esp) = regs.d.edi;
    regs.d.edi = (regs.d.eax+0x00000001);
    regs.x.ax = *(unsigned short *)(regs.d.esi);
    regs.d.ebx ^= regs.d.ebx;
    regs.h.bl = *(unsigned char *)(regs.d.edx);
    regs.d.ecx = regs.d.eax;
    regs.d.ecx &= 0x0000FFFF;
    regs.d.ecx >>= 0x00000008;
    regs.d.ecx ^= regs.d.ebx;
    regs.d.ebx ^= regs.d.ebx;
    regs.h.bh = regs.h.al;
    regs.x.ax = *(unsigned short *)(regs.d.ecx*0x00000002+0x0045F81C);
    regs.x.ax ^= regs.x.bx;
    regs.d.edx++;
    regs.d.edi--;
    *(unsigned short *)(regs.d.esi) = regs.x.ax;
    JNE 401057
    regs.d.edi = *(unsigned int *)(regs.d.esp);
    regs.d.esp += 4;
    regs.d.ebx = *(unsigned int *)(regs.d.esp);
    regs.d.esp += 4;
    label_401083:
    regs.d.esi = *(unsigned int *)(regs.d.esp);
    regs.d.esp += 4;
    return 0x8;
}

Поскольку JNE 401057 не использует CMP или TEST

Как исправить это использование в коде C?


person SSpoke    schedule 14.10.2011    source источник
comment
Если вы пишете эмулятор, вам также придется эмулировать регистр флагов. И в зависимости от того, насколько хитрый код, вам, возможно, придется эмулировать прыжок в середину инструкции.   -  person Raymond Chen    schedule 19.10.2011
comment
На самом деле у меня есть код, который неправильно прыгает всего на 2 байта ... поэтому он переходит в предыдущую инструкцию ... и отображает ее совершенно по-другому. jp short near ptr loc_41FA2B+2 (я разместил это в другом вопросе) решил проблему ручным исправлением. Но выход. Я пытаюсь сгенерировать аналогичный вывод какого-то неизвестного эмулятора, который не использует флаги и при этом отлично работает!   -  person SSpoke    schedule 20.10.2011


Ответы (1)


Самой последней инструкцией, изменяющей флаги, является dec, которая устанавливает ZF, когда ее операнд достигает 0. Таким образом, jne примерно эквивалентна if (regs.d.edi != 0) goto label_401057;.

Кстати: ret 8 не эквивалентно return 8. Операнд инструкции ret — это количество байтов, которое нужно добавить к ESP при возврате. (Он обычно используется для очистки стека.) Это было бы похоже на

return eax;
regs.d.esp += 8;

за исключением того, что полуочевидно, что это не будет работать в C - return делает любой код после него недостижимым.

На самом деле это часть соглашения о вызовах — [ESP+4] и [ESP+8] являются аргументами, передаваемыми функции, а ret очищает их. Это не обычное соглашение о вызовах C; это больше похоже на fastcall или thiscall, учитывая, что функция ожидает значение в ECX.

person cHao    schedule 14.10.2011
comment
Так вы говорите, когда я выскочил 4 байта ака ESI. Мне нужно выскочить еще 8 байт? еще 2 32-битных значения, которые не являются частью регистров? Я всегда думал, что когда происходит CALL, создается новый стек, я думаю, esp +8; это для параметров? быть выскочить? Все в порядке. Мне даже не нужен return 0x8; Я планирую переписать часть подпрограммы с помощью void Func_addr() { ... }, то, как она работает, для меня гораздо важнее. Как насчет обычного RET? Мне нужно что-то добавить в ESP для этого, ну, он также имеет псевдоним RET 0, так что 0, в конце концов, верно? так что RET не при чем? - person SSpoke; 14.10.2011
comment
@SSpoke: ret извлекает эти 8 байтов для вас. Это были аргументы функции — вызывающая сторона была похожа на push arg2 / push arg1 / call 0x401040. Они должны быть очищены либо вызывающим, либо вызываемым пользователем. В этом случае вызываемый объект очищает их. Однако если вы переписываете и вызывающую, и вызываемую, вы можете использовать соглашение о вызовах вашего компилятора и не нужно беспокоиться о стеке — компилятор позаботится об этом за вас. - person cHao; 14.10.2011
comment
@SSpoke: и ret 0 работает так же, как ret - он вставляет обратный адрес в EIP, и все. - person cHao; 14.10.2011
comment
Подождите секунду, вы сказали, что ретрит выдает вам эти 8 байт? ты имел ввиду ret 8? не регулярный ret не так ли? Если это так, то теперь я полностью понимаю эту логику... так что метод вызывающей стороны будет push 0 / push 1 / call 0x401040 (this being ret/ret 0) / add esp, 8 //(clear pushes 0,1 which happens outside the function call) Ну да, мне не нужно беспокоиться об адресах возврата, я думаю, да, это должно сработать. - person SSpoke; 14.10.2011
comment
@SSpoke: Да ... ret 8 выталкивает дополнительные 8 байтов. Обычный ret (без операнда) ничего не извлекает, кроме адреса возврата -- ret 8 извлекает адрес возврата, а затем увеличивает ESP еще на 8. Однако в любом случае это не является частью функции; это просто договоренность о вызовах. Если вы просто повторно реализуете функциональность, вам не нужно беспокоиться об этом, кроме соблюдения соглашения о вызовах, если вам нужно сохранить совместимость со старым кодом. - person cHao; 14.10.2011