代码之家  ›  专栏  ›  技术社区  ›  psihodelia

C-如何在代码段中创建模式以在内存转储中识别它?

  •  4
  • psihodelia  · 技术社区  · 15 年前

    我转储了我的RAM(只是一段代码段),以便找到C函数的位置。我没有映射文件,也不知道引导/初始化例程的确切功能。

    我将程序加载到RAM中,然后如果我转储RAM,就很难准确地找到什么函数。我想使用C源代码中构建的不同模式,在内存转储中识别它们。

    我试图用包含函数名的不同第一个变量启动每个函数,比如:

    char this_function_name[]="main";
    

    但它不起作用,因为这个字符串将被放置在数据段中。

    我有一个简单的16位RISC CPU和一个实验专用的编译器(没有GCC或任何众所周知的编译器)。系统有16MB的RAM,与其他应用程序(引导加载程序、下载程序)共享。几乎不可能找到一个独特的N nops或smth序列。类似于0xabcd。我想在RAM中找到所有的函数,所以我需要在RAM转储中看到的函数的唯一标识。

    代码段的最佳模式是什么?

    7 回复  |  直到 11 年前
        1
  •  7
  •   Richard Pennington    15 年前

    如果是我,我会使用符号表,例如“NMA.OUT GREP MAIN”。得到你想要的任何函数的真实地址。

    如果你真的没有符号表,那就自己做吧。

    struct tab {
        void *addr;
        char name[100];  // For ease of searching, use an array.
    } symtab[] = {
        { (void*)main, "main" },
        { (void*)otherfunc, "otherfunc" },
    };
    

    搜索这个名字,地址会立即指向它。转到地址。;-)

        2
  •  3
  •   Sergey Podobry    15 年前

    如果编译器具有内联asm,则可以使用它创建模式。编写一些nop指令,这些指令可以通过内存转储中的操作码轻松识别:

    MOV r0,r0
    MOV r0,r0
    MOV r0,r0
    MOV r0,r0
    
        3
  •  1
  •   Eli Bendersky    15 年前

    数字常量放在代码段中,编码在函数的指令中。所以你可以试着用一些神奇的数字,比如0千牛肉等等。

    这里是用VisualC++编写的简单C函数的拆卸视图:

    void foo(void)
    {
    00411380  push        ebp  
    00411381  mov         ebp,esp 
    00411383  sub         esp,0CCh 
    00411389  push        ebx  
    0041138A  push        esi  
    0041138B  push        edi  
    0041138C  lea         edi,[ebp-0CCh] 
    00411392  mov         ecx,33h 
    00411397  mov         eax,0CCCCCCCCh 
    0041139C  rep stos    dword ptr es:[edi] 
        unsigned id = 0xDEADBEEF;
    0041139E  mov         dword ptr [id],0DEADBEEFh 
    

    您可以看到0xDeadBeef使其成为函数的源代码。请注意,您在可执行文件中实际看到的内容取决于CPU的结尾(tx.richard)。

    这是一个x86示例。但是RISC CPU(MIPS等)有指令将即时数据移动到寄存器中——这些即时数据也可以有特殊的可识别值(尽管MIPS、IIRC只有16位)。


    psihodelia-越来越难理解你的意图。它只是一个你想找到的函数吗?那你就不能一个接一个地放5个nop去寻找它们吗?您是否控制编译器/汇编程序/链接器/加载程序?你可以使用什么工具?

        4
  •  1
  •   figurassa    15 年前

    正如您所指出的,这:

    char this_function_name[]="main";
    

    …将在堆栈中设置指向包含字符串的数据段的指针。然而,这:

    char this_function_name[]= { 'm', 'a', 'i', 'n' };
    

    ……可能会将所有这些字节放在堆栈中,这样您就能够识别代码中的字符串(我刚刚在我的平台上尝试过)。

    希望这有帮助

        5
  •  1
  •   Brooks Moses    15 年前

    对于您真正的问题,使用一种完全不同的方法,即查找特定的代码块:使用diff。

    用包含问题函数的代码编译一次,用注释掉一次。生成两者的RAM转储。然后,对这两个转储进行比较,以查看更改了什么——这将是新的代码块。(您可能需要对转储进行某种类型的处理,以删除内存地址,以便获得干净的差异,但在这两种情况下,指令的顺序都应该相同。)

        6
  •  1
  •   Clifford    15 年前

    为什么不让每个函数转储自己的地址呢?像这样:

    void* fnaddr( char* fname, void* addr )
    {
        printf( "%s\t0x%p\n", fname, addr ) ;
        return addr ;
    }
    
    
    void test( void )
    {
        static void* fnaddr_dummy = fnaddr( __FUNCTION__, test ) ;
    }
    
    int main (int argc, const char * argv[]) 
    {
        static void* fnaddr_dummy = fnaddr( __FUNCTION__, main ) ;
        test() ;
        test() ;
    }
    

    通过将fnaddr_设为静态,每个函数执行一次转储。显然,您需要调整fnaddr()以支持系统上的任何输出或日志记录。不幸的是,如果系统执行延迟初始化,您将只获得实际调用的函数的地址(这可能足够好)。

        7
  •  0
  •   Ron    15 年前

    可以通过调用同一个伪函数来启动每个函数,例如:

    void identify函数(无符号int标识符) { }

    每个函数都将使用不同的参数(1、2、3,…)调用IdentifyFunction函数。这不会给您一个神奇的映射文件,但是当您检查代码转储时,您应该能够快速找到Identify函数的位置,因为会有很多跳转到该地址的操作。下一步扫描这些跳转,并在跳转之前检查以查看传递了什么参数。然后您可以创建自己的映射文件。对于某些脚本,这应该是相当自动的。