当前位置: 首页 > 知识库问答 >
问题:

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

井兴怀
2023-03-14

通过使用objdump命令,我发现内存中的地址0x02a8contains start路径 ,并且由于C标准,此路径以 0x00byte结尾。

所以我试着写一个简单的C程序来打印这一行(我使用了丹尼斯·尤里切夫(Denis Yurichev)的“RE初学者”一书中的一个示例 - 第24页):

#include <stdio.h>

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

但是我很失望得到了一个分段错误,而不是预期的< code >/lib 64/LD-Linux-x86-64 . so . 2 输出。

我觉得使用这样一个“快速”调用printf而没有说明符或至少没有指针转换很奇怪,所以我尝试使代码更自然:

#include <stdio.h>

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

运行这个之后,我仍然得到一个分割错误。

我不认为这是因为记忆区域有限,因为在书中,第一次尝试都很顺利。我不确定,也许还有一些书中没有提到的东西。

因此,需要一些明确的解释,说明为什么每次我尝试运行程序时,分段错误都会不断发生。

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

共有3个答案

束雅达
2023-03-14

我想这是因为你使用printf的方式:你没有按照设计的方式使用格式参数。

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

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

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

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

尝试使用您的编译器添加额外的标志“-Werror-Wext-Wall-steantic”并请向我们显示错误。

邓鸿彩
2023-03-14

它不再像那样工作了。您可能使用的64位Linux可执行文件是独立于位置的,它们被加载到内存中的任意地址。在这种情况下,ELF文件不包含任何固定的基址。

虽然您可以按照马尔科·博内利的指示创建一个位置相关的可执行文件,但这并不是现代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可执行文件的开头还有另一个技巧:所谓的辅助向量和getauxval

< code>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魔术数字......

#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);
}
瞿健
2023-03-14

令人失望的是,你的《初学者的教育》一书没有首先进入基础知识,还吐出了这些废话。尽管如此,你所做的显然是错误的,让我解释一下为什么。

通常在Linux上,GCC生成独立于位置的ELF可执行文件。这样做是出于安全目的。当程序运行时,操作系统能够把它放在内存的任何地方(任何地址),程序就会正常工作。这种技术被称为地址空间布局随机化,是操作系统的一个特性,现在默认启用。

通常,ELF程序会有一个“基地址”,并且会准确地加载到该地址才能工作。但是,在位置无关的ELF的情况下,“基地址”设置为0x0,操作系统和解释器决定在运行时将程序放在哪里。

当在独立于位置的可执行文件上使用< code>objdump时,您看到的每个地址都不是真实的地址,而是从程序基址的偏移量(只有在运行时才知道)。因此,在运行时不可能知道这样一个字符串(或任何其他变量)的位置。

如果你想让上面的工作,你将不得不编译一个不独立于位置的ELF。你可以这样做:

gcc -no-pie -fno-pie prog.c -o prog
 类似资料:
  • 我有个测试: 当我删除子句并添加一个包含的依赖项(作为库)时,它可以工作: 当我添加它们时(中的Maven依赖项和),IDEA中的编译失败,出现以下消息: 但是Maven build仍然成功! 测试项目可在https://github.com/rpuch/test-resource-jdk9获得

  • 问题内容: 我有一个测试: 它尝试访问。在Java 8中,它起作用了,但是在Java 9中(我在使用Oracle JDK 9),它失败了。从JDK是默认不可用在Java中9。 我正在尝试使用模块描述符访问它: 在这里,我专门请求访问模块(包含)。但是测试仍然失败。 当我删除该子句并添加包含的依赖项(作为库)时,它会起作用: 当我(在Maven的依赖性增加他们两个和),汇编IDEA失败,出现以下消息

  • 问题内容: 我正在运行Mac OS X环境,习惯于使用〜/提供对当前用户目录的访问。 例如,在我的python脚本中,我只是尝试使用 但是想用 尝试运行此文件或目录时出现错误消息。有任何想法吗? 问题答案: 您需要使用 正在当前工作目录中寻找名为“〜”的目录。 还请注意该函数的文档-特别是,您需要正确设置环境变量以确保进行扩展。在大多数情况下,这不会成为问题,但是如果不进行扩展,那可能就是原因。

  • 前面我们给出了一段完整的C语言代码,就是在显示器上输出“C语言中文网”,如下所示: #include <stdio.h> int main() { puts("C语言中文网"); return 0; } 本节我们就来看看如何通过 Dev C++ 来运行这段代码。 Dev C++ 支持单个源文件的编译,如果你的程序只有一个源文件(初学者基本都是在单个源文件下编写代码),那么不用创建

  • 问题内容: 有没有办法在JVM上运行纯C代码? 不通过JNI连接运行,就像您可以通过JRuby运行ruby代码或通过Rhino运行javascript。 如果目前没有解决方案,您会建议我怎么做? 显然,我想使用尽可能多的局部解决方案来实现它。 ANTLR似乎是一个不错的起点,它具有完整的“ ANSI C”语法实现… 我应该使用ANTLR生成的代码在JVM上构建“玩具” VM吗? 问题答案: 201

  • 有没有可能实现经典的Yacc lexer hack来在由ANTLR4生成的C解析器中区分标识符名和类型名,使用标准的C语法(就像在官方的ANTLR4 GitHub repo上找到的一样)? 可以插入到ANTLR4 lexer中的ad-hoc代码似乎相当有限。Terrence Parr在《最终的ANTLR4参考》一书中说: “一直存在的一种常见做法是将解析器的反馈发送给lexer,这样lexer就可