如何从上下文手动递增指令指针?

时间:2014-01-03 16:27:13

标签: c assembly x86 sigsegv

首先,我要说的是,我在这里做的事情是大多数人没有正当理由去做。 99.99%所有段错误应该导致明确的终止,并且在任何但最简单的情况下快乐地处理它们将导致非常糟糕的行为和损坏的堆栈。如果您来到这里寻求解决段错误,请查看以下链接:https://www.securecoding.cert.org/confluence/display/seccode/SIG35-C.+Do+not+return+from+a+computational+exception+signal+handler

那就是说,我正在努力从外部标准实现一个环境,它已经定义了从计算逻辑错误的信号处理程序返回的行为,因为它跳过了一条指令。我明白这很糟糕,但是我无法控制它;我不能仅仅定义这个定义,因为它是一个嵌入式系统,其中已经编写了其他软件元素,这些元素依赖于定义的行为(它们通常是安全关键的,并且需要能够优雅地退出,即使它们不合适或者可怕的事情;进一步我没有源,所以我不能只修复段错误,任何现有的坏段错误/崩溃行为实际上是期望的,因为我正在模拟现有系统的行为)。

虽然系统本身是在具有固定指令长度的PowerPC上运行,但我们的开发是在并行x86 / x64环境中进行的,其中指令不是固定长度。我知道以下代码可以正常工作,尽管x86很糟糕:

#define _GNU_SOURCE
#include <signal.h>
#include <stdio.h>
#include <ucontext.h>
#include <sys/mman.h>

#define CRASHME *((int*)NULL) = 0 
//for x86
#ifdef REG_EIP
#define INCREMENT(x) (x)->uc_mcontext.gregs[REG_EIP]++
//for x64
#elif defined REG_RIP
#define INCREMENT(x) (x)->uc_mcontext.gregs[REG_RIP]++
//for PPC arch
#elif defined PT_NIP
#define INCREMENT(x) (x)->uc_mcontext.uc_regs->gregs[PT_NIP]+=4
#endif

static void handler(int sig, siginfo_t *si, void *vcontext)
{
    ucontext_t *context = (ucontext_t *)vcontext;
    INCREMENT(context);
}

void crashme_function(void)
{
    printf("entered new context, segfaulting!\n");
    CRASHME;
    printf("SEGFAULT handled!\n");
}

int main (int argc, char* args)
{
    struct sigaction sa;
    printf("Printing a thing\n");
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = handler;
    sigaction(SIGSEGV, &sa, NULL);
    printf("Entering new context...\n");
    crashme_function();
    printf("context exited successfully\n");
    return(0);

}

执行此代码的结果将在运行Linux内核3.11.X的基于intel的arch上将指令指针提前1,最终它将超出指令。我知道这可能不适用于所有指令。实际上,当在我的测试环境中执行时,处理程序进入6次(对于指令的6个字节),然后执行继续经过CRASHME。

在给定现有指令的情况下,仅仅将给定指令指针推进到下一条指令似乎是一项微不足道的任务;处理器每个周期都会这样做。在其他设置中,它被称为“查看指令表并构建自己的”或“实现反汇编程序”。这些对于任务来说既不合适也不必要,因为两者都已经由其他人完成并且(几乎?)仅在我的工作计算机无法访问的网站上发布(并且几乎没有),并且我不信任我提交我的家PC。但是,我在哪里可以找到这样的表或库来完成指令计算,而不是查看我已经知道我无法访问的站点?

2 个答案:

答案 0 :(得分:3)

Linux内核源代码具有X86操作码映射的编码,然后由Awk脚本解析以生成一组可用于读取指令的表。它有足够的信息为您提供准确的指令大小,但您可能需要扩展它以包含浮点指令和一些较新的Intel扩展(如AVX)的信息。

如果您有权访问linux内核源代码树,请查看arch / x86 / lib / x85-opcode-map.txt。

包含确定指令大小所需的所有数据。

有一个AWK脚本@ arch / x86 / tools / gen-insn-attr-x86.awk,它将读取操作码文件并生成一系列表格,用于对操作码映射中的信息进行编码。

最后,如果你看一下arch / x86 / lib / insn.c,那里有一个函数insn_get_length(...),它将使用从操作码映射生成的表格为你提供指令的长度。这应该足以让你回答你的特定问题“这条指令有多大”。

关于该代码没有特别“kernely”。您可以在不做任何特殊操作的情况下适应用户模式

我假设访问Linux内核源代码不应该是一个安全问题,并且没有什么可以阻止您阅读/采用GPL代码。

答案 1 :(得分:0)

您可以使用libdisasm等库,它可以为您提供所需的信息。我已经做了类似的挂钩,编写我自己的反汇编程序,但我想使用一个现成的库更容易。

无论如何,正如我在评论中写的那样,我不确定你认为你从中获得了什么。你真的不希望以自动的方式分析这个程序,如果你想要它用于崩溃恢复,我已经说过这个原因非常不可靠,并且可能会导致比解决更多的问题。

也许你真正需要的是一些虚拟机。

为了执行代码,你还需要一些程序集,它会将寄存器恢复到发生异常时的状态,然后跳转到你指向的位置。