如何在运行时在C语言中访问解释器路径地址?

问题描述

通过使用objdump命令,我发现内存中的地址0x02a8包含起始路径/lib64/ld-linux-x86-64.so.2,并且该路径以0x00字节结尾,这是由于C标准。

因此,我试图编写一个简单的C程序来打印此行(我使用了Denis Yurichev的“ RE for Beginners”一书中的示例-第24页):

#include <stdio.h>

int main(){
    printf(0x02a8);
    return 0;
}

但是我对于遇到分段错误而不是预期的/lib64/ld-linux-x86-64.so.2输出感到失望。

使用printf这样的“快速调用而没有指定符或至少没有指针强制转换,我感到很奇怪,因此我尝试使代码更加自然:

#include <stdio.h>

int main(){
    char *p = (char*)0x02a8;
    printf(p);
    printf("\n");
    return 0;
}

运行此命令后,我仍然遇到细分错误

我不认为这是由于内存区域有限而发生的,因为在本书中,第一次尝试就一切顺利。我不确定,也许那本书中没有提到更多。

因此,需要对每次我尝试运行该程序时为什么会不断发生分段错误的原因进行清楚的解释。

我正在使用最新的完全升级的Kali Linux版本。

解决方法

令人失望的是,您的“ RE for Beginners”一书没有首先涉及基础知识,并且吐出了这些废话。但是,您的操作显然是错误的,让我解释一下原因。

通常在Linux上,GCC会生成position independent的ELF可执行文件。这样做是出于安全目的。当程序运行时,操作系统可以将其放置在内存中的任何位置(任何地址),并且程序可以正常运行。此技术称为Address Space Layout Randomization,是操作系统的一项功能,目前默认情况下已启用。

通常,ELF程序将具有“基地址”,并且将被完全加载到该地址才能正常工作。但是,如果是位置无关的ELF,则将“基地址”设置为0x0,并且操作系统和解释器会决定在运行时将程序放在何处。

在位置无关的可执行文件上使用objdump时,您看到的每个地址都是 不是真实地址 ,而是相对于程序(仅在运行时才知道)。因此,在运行时无法知道这样的字符串(或任何其他变量)的位置。

如果您希望以上操作有效,则必须编译与 位置无关的ELF。您可以这样做:

gcc -no-pie -fno-pie prog.c -o prog
,

它不再那样工作。您可能使用的64位Linux可执行文件与位置无关,并且它们以任意地址加载到内存中。在这种情况下,ELF文件不包含任何固定的基址。

虽然您可以制作一个位置相关的可执行文件as instructed by Marco Bonelli,但在现代64位linuxen上,该文件不适用于任意可执行文件,因此,更值得学习使用位置相关的 ,但这有点棘手。

这对我来说可以打印ELF,即精灵头魔术和解释器字符串。这样做很脏,因为它可能始终只适用于小型可执行文件。

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

int main(){
    // convert main to uintptr_t
    uintptr_t main_addr = (uintptr_t)main;

    // clear bottom 12 bits so that it points to the beginning of page
    main_addr &= ~0xFFFLLU;

    // subtract one page so that we're in the elf headers...
    main_addr -= 0x1000;

    // elf magic
    puts((char *)main_addr);

    // interpreter string,offset from hexdump!
    puts((char *)main_addr + 0x318);
}

还有另一个技巧可以在内存中找到ELF可执行文件的开头:所谓的auxiliary vector and getauxval

getauxval()函数从辅助向量中检索值, 内核的ELF二进制加载程序用来传递某些信息的机制 执行程序时向用户空间提供信息。

ELF 程序头在内存中的位置将是

#include <sys/auxv.h>
char *program_headers = (char*)getauxval(AT_PHDR);

实际的ELF标头的长度为64个字节,程序标头从字节64开始,因此如果您从中减去64,则会再次获得指向魔术字符串的指针,因此我们的代码可以简化为

#include <stdio.h>
#include <inttypes.h>
#include <sys/auxv.h>


int main(){
    char *elf_header = (char *)getauxval(AT_PHDR) - 0x40;
    puts(elf_header + 0x318); // or whatever the offset was in your executable
}

最后,一个可执行文件仅从ELF标头中找出解释器的位置,前提是您有一个64位ELF,即Wikipedia的幻数...

#include <stdio.h>
#include <inttypes.h>
#include <sys/auxv.h>


int main() {
    // get pointer to the first program header
    char *ph = (char *)getauxval(AT_PHDR);

    // elf header at this position
    char *elfh = ph - 0x40;

    // segment type 0x3 is the interpreter;
    // program header item length 0x38 in 64-bit executables
    while (*(uint32_t *)ph != 3) ph += 0x38;

    // the offset is 64 bits at 0x8 from the beginning of the 
    // executable
    uint64_t offset = *(uint64_t *)(ph + 0x8);

    // print the interpreter path...
    puts(elfh + offset);
}
,

我猜想它是段错误的原因是您使用printf的方式:您没有使用format参数的设计格式。

当您想使用printf函数读取数据时,它使用的第一个参数是一个字符串,该字符串将格式化显示的工作方式int printf(char * fmt,...)“ ...表示您所要访问的数据想要根据格式字符串参数显示

因此,如果您要打印字符串 //格式为文本

  printf("%s\n",pointer_to_beginning_of_string);

// 如果这不起作用,则可能是因为您试图读取不应该访问的内存。

尝试在编译器中添加额外的标志“ -Werror -Wextra -Wall -pedantic”,然后向我们显示错误。