克隆汇编指令所需的完整信息是什么

时间:2012-02-13 08:10:00

标签: assembly x86 hook

我想创建读取汇编指令的代码(仅限x86)并在其他内存位置重新创建它们以生成钩子代码。就像,我想挂钩函数X所以我需要用跳转修补它的(至少)第一个字节和我替换的每个指令(根据汇编代码可能有所不同)(部分或不是)我需要在内存中重新创建我的块,然后添加一条指令,从我没有触摸的下一条指令的偏移量跳回到原始函数X.你可能知道我在说什么,因为它对许多人来说并不新鲜。我不想制作一个完整的完美程序,但我想制作一个完全可扩展的代码库,它将使用一棵树,我将在下面解释。首先让我们想象一些指示:

  • A - “0x12 0x13 ..”此指令有4个字节,前两个是静态的。
  • B - “0x12。”此指令有2个字节,第一个是静态的。

对于这种情况,我会有一棵看起来像

的树
    Tree
     |
     |
    0x12
   /  \
  B   0x13
        |
        A

因此,当代码解析一条指令时,它会尝试使用最长的前缀到达指令,如果失败则可以停止或失败,或者在树中尝试上面的指令。

想要做出类似这样的事情的原因是我可以稍后使用dll提供的指令进行扩展,这是我正在做的事情的必要条件,因为我希望能够更快地发布代码来处理90%的指令和只有在我将来需要的情况下才能照顾那些更先进的。

所以,现在我的问题是:处理代码指令的DLL需要的确切完整信息是什么? 喜欢:

  • 指令开始的地址。 (当然必须)
  • ?包含指令开始地址的模块的基址(假设指令引用其模块内存的某些部分,我认为这是必需的)
  • ?先前的指示。不知道是否有指令需要知道它之前的指令或类似的内容

我还想问一下树结构是否正常,或者我是否会遇到问题。

所以,基本上我想请你帮忙决定创建最通用的可能代码所需的信息是什么:

给定一个地址,解析其汇编指令,并根据指令调用将复制这些指令的dll中的函数指针。

所以,有点像

void* copy_instructions(void* address,int& len)
{
    int bytes_copied = 0;
    void* instructions = block of bytes // don't care about the implementation

    do
    { 
        void (*copy_instruction)(void*,int*) = get_a_handler_to_instruction_at(address) // this function will use the tree structure and retrieve a function from a dll

        if(copy_instruction != NULL)

            int len = 0;
            void* instruction = copy_instruction(void* address,&len,...) // I want to know how to make this function complete in terms of what it need for every case

            if(!instruction)
                fail

            instructions += instruction // don't care about the implementation

            address += len
            bytes_copied += len
        else
                fail
    }
    while(bytes_copied < 5)

    add_instructions_jump_to(instructions,address + bytes_copied)

    len = bytes_copied;

    return 
}

我的问题是:

完整的“copy_instruction”函数标题怎么样? 上面提到的树是否可以实现“get_a_handler_to_instruction_at”或者我需要其他东西。

1 个答案:

答案 0 :(得分:3)

为了挂钩你需要的功能:

  1. 知道它的接口(调用约定,参数号和类型,所有这些)。在优化代码时,编译器可以内联函数或欺骗界面。如果是这种情况,我不知道如何最好地处理它。您可能需要调整代码,以便通过指向函数的易失性指针调用函数,试图说服编译器指针可以随时更改其值并指向具有相同参数的任何其他函数,这将是不明智的改变功能的序幕和结语。禁用优化可能是另一种选择。所有这些都是为了避免原始函数和新函数在接收参数和返回方面不兼容的情况。但是,如果这是导出函数之一,编译器显然不会更改任何内容,因为它会破坏代码。
  2. 知道它的地址。
  3. 最小程度地反汇编函数的第一条指令,您将使用跳转指令覆盖新代码。在反汇编时,你必须找出:指令长度(为此你需要正确解析所有指令前缀,所有操作码字节,所有Mod / Rm / SIB字节,所有位移和所有立即操作数字节;一些逻辑+查找表将帮助),该指令是否将控制转移到或访问相对于指令指针的位置的数据(例如JccJMP nearCALL nearJMP/CALL qword ptr [RIP+something],{{ 1}}),如果是这样,则为目标地址。
  4. 了解原始说明副本的地址。理想情况下,您在解析说明后会为副本分配内存,但您可以(并且可能应该)预先分配更多内容以简化您的生活。
  5. 将原始说明复制到新位置,如有必要,请根据这些说明的旧位置和新位置之间的差异调整其中的相对地址。注意,原始指令可以在其中使用非常短的相对地址(例如,8位(MOV EAX, dword ptr [RIP+something]的最常见情况)或甚至16位),这对于简单的直接修补来说是不够短的。在这种情况下,您需要使用较长的相对地址重新组合此类指令(这将需要插入/更改指令前缀或更改Mod / RM / SIB字节)。请记住,相对地址是相对于指令的结尾(或IOW,下一条指令的开始),这意味着如果调整后的指令比原始指令长,则相对地址必须考虑指令长度差异为好。理想情况下,当你覆盖的原始指令彼此jmp时,你也应该准备好处理这个案例。您不希望他们的副本跳回到覆盖的代码。
  6. 添加Jcc指令,跳转到原始函数中的第一个未触及(通过覆盖)指令。
  7. 在此之后,在大多数情况下,挂钩应该正常工作。如果编译器生成的任何其他代码需要原始指令在原始位置并保持不变,则会出现问题。

    对于数据结构,您将替换原始代码的JMP个字节。对于32位跳转,N为5。那些N字节将对应于最多N个原始指令。您需要完整地保存那些1到N个指令(每个指令最多15个字节,IIRC),然后解析,可能调整并存储在新位置。你这里真的不需要一棵树,一个数组就足够了。每条指令的元素。简单。但是这些代码需要仔细编写和调试/测试。

    请参阅相关问题。可能有宝贵的细节。

    编辑:回答主要问题:

    我认为,“复制”所有指令(copy_instructions())的主要功能可能确实是在您定义它时定义的。但是,您可能希望从中返回错误代码,以防它失败(分配内存或反汇编未知指令或其他内容)。它可能会有所帮助。我无法看到你/呼叫者需要的其他内容。