【问题标题】:Assembly how to translate JNE to C Code without ZF flag access汇编如何在没有 ZF 标志访问的情况下将 JNE 转换为 C 代码
【发布时间】:2011-12-07 10:29:55
【问题描述】:

ASM 到 C 代码的模拟几乎完成了.. 只是试图解决这些第二遍问题。

假设我得到了这个 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 不使用CMPTEST

如何在 C 代码中解决这个问题?

【问题讨论】:

  • 如果你正在编写一个模拟器,你也必须模拟标志寄存器。并且根据代码的偷偷摸摸程度,您可能必须模拟跳转到指令中间。
  • 事实上,我的代码仅不正确地跳转了 2 个字节.. 所以它进入了前一条指令.. 并以完全不同的方式呈现它。 jp short near ptr loc_41FA2B+2(我在另一个问题中发布了这个)通过手动修补解决了这个问题。但是输出。我正在尝试生成类似于某些未知模拟器的另一个输出,它没有使用标志并且仍然完美运行!

标签: c assembly x86 machine-code code-translation


【解决方案1】:

最近修改标志的指令是dec,它在操作数为0时设置ZF。所以jne大约相当于if (regs.d.edi != 0) goto label_401057;

顺便说一句:ret 8 不等于 return 8ret 指令的操作数是返回时添加到 ESP 的字节数。 (通常用于清理堆栈。)有点像

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

除了很明显之外,这在 C 中不起作用——return 使之后的任何代码都无法访问。

这实际上是调用约定的一部分——[ESP+4][ESP+8] 是传递给函数的参数,ret 正在清理它们。这不是通常的 C 调用约定;考虑到函数需要 ECX 中的值,它看起来更像 fastcall 或 thiscall。

【讨论】:

  • 所以你是说当我弹出 4 个字节又名 ESI 时。我需要再弹出8个字节吗?还有 2 个不属于寄存器的 32 位值?我一直认为当CALL 出现时,会创建一个新堆栈,我猜是+8;是为了参数吗?被弹出?没关系.. 我什至不需要返回 0x8;我计划用void Func_addr() { ... } 重写子程序部分,它的执行方式对我来说更重要。普通的RET 怎么样?我是否需要为此向 ESP 添加任何内容,它也被别名为 RET 0 所以 0 毕竟好吗?所以与 RET 无关?
  • @SSpoke:ret 会为您弹出这 8 个字节。它们是函数的参数——调用者就像push arg2 / push arg1 / call 0x401040。它们必须由调用者或被调用者清理。在这种情况下,被调用者正在清理它们。但是,如果您同时重写调用者和被调用者,则可以使用编译器的调用约定,而无需担心堆栈——编译器会为您处理。
  • @SSpoke: 和ret 0 的作用与ret 相同——它将返回地址弹出到EIP 中,仅此而已。
  • 等一下,你说 ret 会为你弹出这 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) 好吧,我不需要担心返回地址,我相信,是的,应该这样做。
  • @SSpoke:是的...ret 8 弹出额外的 8 个字节。普通的ret(没有操作数)除了返回地址不会弹出任何东西——ret 8 会弹出返回地址,然后再将 ESP 提高 8 个。不过,无论哪种方式,它都不是函数的一部分。这只是电话会议的东西。如果您只是重新实现功能,则无需担心,如果您需要保持与旧代码的兼容性,则只需注意调用约定。
猜你喜欢
  • 1970-01-01
  • 2020-02-06
  • 2020-11-04
  • 2019-10-20
  • 2022-11-21
  • 2014-09-01
  • 1970-01-01
  • 2011-12-07
  • 1970-01-01
相关资源
最近更新 更多