【问题标题】:Arbitrary code execution using existing code only仅使用现有代码执行任意代码
【发布时间】:2013-08-21 14:53:36
【问题描述】:

假设我想执行任意 mov 指令。我可以编写以下函数(使用 GCC 内联汇编):

void mov_value_to_eax()
{
    asm volatile("movl %0, %%eax"::"m"(function_parameter):"%eax");
    // will move the value of the variable function_parameter to register eax
}

而且我可以制作这样的函数,该函数将适用于所有可能的寄存器。 我的意思是——

void movl_value_to_ebx() { asm volatile("movl %0, %%ebx"::"m"(function_parameter):"%ebx"); }
void movl_value_to_ecx() { asm volatile("movl %0, %%ecx"::"m"(function_parameter):"%ecx"); }
...

以类似的方式,我可以编写函数,将任意地址的内存移动到特定的寄存器,并将特定的寄存器移动到内存中的任意地址。 (mov eax, [memory_address]mov [memory_address],eax)

现在,我可以随时执行这些基本指令,因此我可以创建其他指令。例如,将一个寄存器移动到另一个寄存器:

function_parameter = 0x028FC;
mov_eax_to_memory(); // parameter is a pointer to some temporary memory address
mov_memory_to_ebx(); // same parameter

所以我可以解析一条汇编指令,并根据它决定使用哪些函数,如下所示:

if (sourceRegister == ECX) mov_ecx_to_memory();
if (sourceRegister == EAX) mov_eax_to_memory();
...
if (destRegister == EBX) mov_memory_to_ebx();
if (destRegister == EDX) mov_memory_to_edx();
...

如果它可以工作,它允许你执行任意 mov 指令。

另一种选择是创建一个要调用的函数列表,然后遍历列表并调用每个函数。也许它需要更多的技巧来制作像这样的等效指令。

所以我的问题是:是否有可能为所有(或部分)可能的操作码制作这样的东西?它可能需要编写很多函数,但是是否有可能制作一个解析器,它会根据给定的汇编指令以某种方式构建代码,然后执行它,或者那是不可能的?

编辑:您无法更改内存保护或写入可执行内存位置。

【问题讨论】:

  • 这样的问题引出了一个问题,“为什么要做这样的事情?”
  • 因为如果它正在工作,如果您无法将代码写入内存,它可以成为执行代码的一种方式。 (操作系统可能会阻止下载和执行代码)

标签: c memory assembly x86 execution


【解决方案1】:

我真的不清楚你为什么要问这个问题。首先,这个函数……

void mov_value_to_eax()
{
    asm volatile("movl %0, %%eax"::"m"(function_parameter):"%eax");
    // will move the value of the variable function_parameter to register eax
}

...使用 GCC 内联汇编,但函数本身不是内联的,这意味着会有序言和尾声代码包装它,这可能会影响您的预期结果。您可能想要使用 GCC 内联汇编函数(与包含 GCC 内联汇编的函数相反),这可能会让您更接近您想要的,但仍然存在问题.....

好的,假设您为每个可能的 x86 操作码(至少 GCC 汇编器知道的那些)编写了一个 GCC 内联汇编函数。现在假设您想以任意顺序调用这些函数来完成您可能希望完成的任何事情(考虑到哪些操作码在环 3(或您正在编码的任何环中)是合法执行的)。您的示例向您展示了使用 C 语句对逻辑进行编码以确定是否调用内联汇编函数。猜猜看:那些 C 语句正在使用处理器寄存器(甚至可能是 EAX!)来完成它们的任务。无论您想通过调用这些任意的内联汇编函数来做什么,都会被编译器发出的逻辑汇编代码(if (...) 等)踩在脚下。反之亦然:您的内联汇编函数任意指令正在踩踏编译器发出的指令期望不会踩踏的寄存器。结果不太可能在不崩溃的情况下运行。

如果你想用汇编写代码,我建议你直接用汇编写,然后用 GCC 汇编器来汇编。或者,您可以在 asm() 语句中编写整个 C 可调用汇编函数,并根据需要从您的 C 代码中调用它们。但是您编写的 C 可调用汇编函数需要在您正在使用的调用约定 (ABI) 的规则内运行:如果您的汇编函数使用被调用者保存的寄存器,则您的函数需要将原始值保存在该寄存器中(一般在栈上),然后在返回给调用者之前恢复它。

【讨论】:

  • 我知道。您必须预先保存和恢复一些寄存器。我只是想知道它是否可以这样工作。
  • @Tomer 你对这种“工作”有什么了解?它会为您生成不受干扰的操作码吗?很可能不会,即使会——这个操作码也不是最优的。
  • 我的意思是它将执行给定的代码。没关系。如果代码说要显示一条消息 - 它会显示一条消息。
【解决方案2】:

...好的,根据您的评论Because if it's working it can be a way to execute code if you can't write it to memory. (the OS may prevent it)....

当然,您可以执行任意指令(只要它们对于您正在运行的任何环都是合法的)。 JIT 还能如何工作?您只需要调用操作系统系统调用来设置您的指令所在的内存页面的权限......将它们更改为“可执行”,然后调用它们!

【讨论】:

  • 但您无法更改内存保护。正是这个想法。
  • @Tomer - 您的帖子没有提及您使用的是什么操作系统,但由于您使用的是 GCC,我假设您使用的是 Linux。在这种情况下,只需分配(通过newmalloc())一个大小合适的缓冲区,您可以在其中写入指令。使用系统调用接口mprotect() 将缓冲区的权限更改为读+写+执行,然后您可以通过将入口地址转换为指向函数的指针来运行指令,然后取消引用指针(“调用它” )。当然如果你想让它返回,你必须在你的 asm 代码中观察你的 ABI 的函数调用规则。
  • @Tomer - ..."大小合适的缓冲区":您会希望缓冲区在页面边界开始和结束。确保这一点的一种技术是分配比您需要的更多(按 PAGE_SIZE-1 字节),然后将返回的指针对齐到下一页边界。如果您查看 mprotect() 手册页,它可能提供了如何完成此操作的示例。
  • @Tomer - 如果您使用 GCC 为运行另一个操作系统或无操作系统的嵌入式系统进行编译,您仍然可以这样做。您的其他操作系统可能有一个系统调用,可让您设置内存页面的页面权限。如果你在一个无操作系统的嵌入式系统上,你只需要创建一段内存,其属性包括读+写+执行权限。也许您的项目使用“链接器命令文件”或“链接器脚本”来指定内存部分并定义每个部分的属性......如果是这样,那就是这样做的地方。
  • 从 iOS App Store 下载的应用不能做这样的事情。
猜你喜欢
  • 2011-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多