从字节数组发出内联程序集

Emit inline assembly from byte array

本文关键字:程序集 字节 字节数 数组      更新时间:2023-10-16

我在C++函数中有这些参数:

unsigned char originalBytes[], int originalBytesLength

而这个循环来发出字节:

for (int i = 0; i < originalBytesLength; i++)
    __asm _emit originalBytes[i];

但是,我收到一条错误消息,说最后一行"Improper operand type (C2415)"。有没有办法在不对字节进行硬编码的情况下发出字节?

我试图做的是将一个字节数组传递给函数,并让它使用内联程序集发出字节。因此,每次调用的字节可能不同。

这可能没有你认为的那样。

首先,你不能在循环中使用__asm _emit,它不会"知道"围绕它的代码。这就像如果需要,尝试在循环中使用 #include,它只会被处理一次。

最重要的是,_asm _emit不是你可以在运行时使用的东西,也不是一个变量,甚至不是constexpr。这只是一种在编译时在应用程序中转储原始字节文本的方法,一次。所以真的没有一种干净的方法来做你想用__asm _emit做的事情。您必须在编译时逐字节发出该缓冲区,或者使用更方便的内联 ASM 块。所以不,如果不对字节进行硬编码,就无法发出字节。

另请注意,编译器不会尝试理解您刚刚在二进制文件中转储的字节的含义。如果您正在修改寄存器或编写与位置相关的代码,这可能会与编译器正在执行的操作发生冲突并导致可怕的错误。

编辑:

我试图做的是将一个字节数组传递给函数,并让它使用内联程序集发出字节。因此,每次调用的字节可能不同。

你不能用__asm _emit做到这一点。实际上,在运行时没有简单的方法可以做到这一点,因为每次发出一个字节时,都必须增加二进制文件。一旦二进制文件已经编译好,这真的很难。

如果您有重定位信息,并且已准备好挂起所有线程,修复所有重定位,将不再适合的短跳重写为长跳(并重新修复重定位,并在循环中重新修复 jmps),这是可能的。然后修复这些部分,如有必要,重新定位其他线程并恢复。我听说过恶意软件这样做(简化版本),但仅此而已。

也就是说,您可能想看看各种 JIT 库。我知道LLVM和GGC有一个。你不会得到完全相同的结果,但 JIT 可能是你想要的。您将获得一个字节数组,您可以使用函数指针跳转到该数组,而不是在运行时在任意位置发出的代码。

如果您确实要执行字节,并且它们在运行时之前是未知的,则可以将它们写入内存的读取/执行区域,然后调用该区域的基数。 非常确定这是您想要做的,并且非常确定这些字节的来源。 除了非常特殊的情况之外,我不建议这样做。 以下是您在窗口中执行此操作的方法。

typedef __cdecl void (* voidf_t)(); // make sure the calling convention is correct
void execute(LPBYTE bytes, DWORD len_bytes) {
    // bytes on the stack are NX on most modern operating systems, get some executable memory
    LPBYTE exec_region = (LPBYTE) VirtualAlloc(NULL, len_bytes, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if(exec_region != NULL) {
        memcpy(exec_region, bytes, len_bytes);
        voidf_t fun = exec_region;
        fun(); // this will call the bytes
        VirtualFree(exec_region, 0, MEM_RELEASE);
        fun = exec_region = NULL;
    }
}

使用此类技术时需要考虑许多因素。 您必须知道正在执行的字节来自何处。 您正在执行的字节也必须是返回的函数,并且期望使用 typedef 中使用的约定进行调用。 代码还必须完全独立于位置,因为没有执行重定位修正。 通常情况下,像这样的代码最终会成为应用程序中漏洞和/或不稳定的根源,所以我敦促你考虑是否有更好的解决方案来解决你的问题。