7

我一直在研究一个(C++)项目,它需要完全动态分配的函数,这意味着 malloc/new 和 mprotect 然后手动修改缓冲区以汇编代码。因此,我确切地想知道,我的这个“缓冲区”需要什么,才能复制任何其他 _cdecl 函数。例如:

int ImAcDeclFunc(int a, int b)
{
     return a + b;
}

如果我想从字面上创建这个函数的副本,但完全是动态的,那需要什么(记住它是带有内联汇编的 C++)?对于初学者,我想我必须做这样的事情(或类似的解决方案):

// My main....
byte * ImAcDeclFunc = new byte[memory];
mprotect(Align(ImAcDeclFunc), pageSize, PROT_EXEC | PROT_READ | PROT_WRITE);

在此之后,我将不得不找出ImAcDeclFunc(int a, int b);. 现在我在汇编方面仍然很糟糕,那么这个函数在 AT&T 语法中的表现如何呢?这是我的大胆尝试:

push %ebp
movl %%ebp, %%esp
movl 8(%ebp), %%eax
movl 12(%ebp), %%edx
addl edx, eax
pop ebp
ret

现在如果这个代码是正确的(我非常怀疑,请纠正我)我只需要找到这个代码的十六进制值(例如,'jmp' 是 0xE9 和 'inc' 是 0xFE),并直接使用这些值C++?如果我继续我以前的 C++ 代码:

*ImAcDeclFunc = 'hex value for push'; // This is 'push' from the first line
*(uint)(ImAcDeclFunc + 1) = 'address to push'; // This is %ebp from the first line
*(ImAcDeclFunc + 5) = 'hex value for movl' // This is movl from the second line
// and so on...

在我为整个代码/缓冲区完成此操作后,对于完全动态的 _cdecl 函数是否足够(即我可以将它转换为函数指针并执行int result = ((int (*)(int, int))ImAcDeclFunc)(firstArg, secondArg)?)。而且我对使用 boost::function 或类似的东西不感兴趣,我需要该函数是完全动态的,因此我感兴趣:)

注意:这个问题是我上一个问题的延续,但有更多细节。

4

3 回答 3

6

如果你拿这个lala.c

int ImAcDeclFunc(int a, int b)
{
    return a + b;
}

int main(void)
{
    return 0;
}

你可以用gcc -Wall lala.c -o lala. 然后,您可以使用objdump -Dslx lala >> lala.txt. 你会发现ImAcDeclFunc被组装成:

00000000004004c4 <ImAcDeclFunc>:
ImAcDeclFunc():
  4004c4:   55                      push   %rbp
  4004c5:   48 89 e5                mov    %rsp,%rbp
  4004c8:   89 7d fc                mov    %edi,-0x4(%rbp)
  4004cb:   89 75 f8                mov    %esi,-0x8(%rbp)
  4004ce:   8b 45 f8                mov    -0x8(%rbp),%eax
  4004d1:   8b 55 fc                mov    -0x4(%rbp),%edx
  4004d4:   8d 04 02                lea    (%rdx,%rax,1),%eax
  4004d7:   c9                      leaveq 
  4004d8:   c3                      retq   

其实这个功能比较容易复制到别处。在这种情况下,您完全正确地说可以复制字节并且它会起作用。

当您开始使用将相对偏移量作为操作码一部分的指令时,就会出现问题。例如,相对跳跃或相对呼叫。在这些情况下,您需要正确地重新定位指令,除非您碰巧能够将其复制到与原始地址相同的地址。

简而言之,要重新定位,您需要找到它最初的基础,并计算与您将要建立的基础的差异,并根据该偏移量重新定位每个相关指令。这本身是可行的。您真正的困难是处理对其他函数的调用,尤其是对库的函数调用。在这种情况下,您需要保证库已链接,然后以您所针对的可执行格式定义的方式调用它。这是非常重要的。如果您仍然感兴趣,我可以指出您应该阅读的方向。


在上面的简单情况下,您可以这样做:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <sys/mman.h>
#include <unistd.h>

int main(void)
{
    char func[] = {0x55, 0x48, 0x89, 0xe5, 0x89, 0x7d, 0xfc,
    0x89, 0x75, 0xf8, 0x8b, 0x45, 0xf8,
    0x8b, 0x55, 0xfc, 0x8d, 0x04, 0x02,
    0xc9, 0xc3};

    int (* func_copy)(int,int) = mmap(NULL, sizeof(func),
        PROT_WRITE | PROT_READ | PROT_EXEC,
        MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

    memcpy(func_copy, func, sizeof(func));
    printf("1 + 2 = %d\n", func_copy(1,2));

    munmap(func_copy, sizeof(func));
    return EXIT_SUCCESS;
}

这在 x86-64 上运行良好。它打印:

1 + 2 = 3
于 2012-05-04T21:57:20.853 回答
1

我认为将一些脚本语言嵌入到您的项目中而不是编写自修改程序会更好。这将花费更少的时间,您将获得更大的灵活性。

如果我想从字面上创建这个函数的副本,但完全是动态的,那需要什么(记住它是带有内联汇编的 C++)?

它需要带有反汇编程序的人。从技术上讲,函数应该从一个地址开始并在 return 语句结束。但是,尚不清楚编译器在优化阶段对该函数做了什么。如果函数入口点位于某种奇怪的位置(例如在函数末尾,在 return 语句之后),或者如果函数被拆分为与其他函数共享的多个部分,我不会感到惊讶。

于 2012-05-04T23:03:29.847 回答
1

您可能想查看 GNU 闪电:http ://www.gnu.org/software/lightning/ 。它可能会帮助您完成您正在尝试做的事情。

于 2012-05-04T21:45:35.790 回答