关于编译器和内存理论的 C 问题 [已关闭]

C problem about Compiler and Memory Theory [closed]

提问人:SugarFree 提问时间:11/1/2023 最后编辑:CliffordSugarFree 更新时间:11/1/2023 访问量:125

问:


想改进这个问题吗?通过编辑这篇文章添加详细信息并澄清问题。

22天前关闭。

在不调用任何“call”或“jump”函数的情况下,我们需要按照“这是第一个”和“这是第二个”的顺序获得输出。在我看来,我们需要使用“粗体”来使用内存和指令。我们也不能称之为“学习”的功能。

#include <stdio.h>

void study()
{
    printf("this is the second.\n");
}

void study2()
{
    int bold[4];
    // can only modify this section BEGIN
    // cant call study(), maybe use study(pointer to function)


    // can only modify this section END
    printf("this is the first\n");
}

int main(int argc, char *argv[])
{
    study2();
    return 0;
}
C 内存 编译器构造 缓冲区溢出 指令

评论

1赞 Barmar 11/1/2023
字符串文本的位置与函数的地址无关。您需要以某种方式找到构造 的参数列表的指令,并在其中找到第一个参数的地址。printf
2赞 Peter Cordes 11/1/2023
ROP 攻击不是编译器理论的问题,而是实际实现细节和特定 ISA 的特定编译器版本的特定构建的 asm 问题。据推测,如果编译时没有优化,可能会产生在某处存储新返回地址的 asm。但它是编译器可见的未定义行为,因此现代编译器可能会将其编译为 x86(非法入侵)或其他东西。bold[5] = 0x12345;ud2
1赞 Peter Cordes 11/1/2023
@Barmar:我认为赋值的目的是通过越界访问在堆栈上写入两个新的返回地址,然后执行一个(将两个中较低的一个弹出到程序计数器中),这两个地址分别位于中间和中间,在中间添加的语句之后的某个地方。(所以它需要像 这样的条件返回,否则死代码消除将删除 .如果“调用或跳转函数”应该是“语句”或“指令”,这仍然符合要求。bold[]return;study()study2()return;if(!static_var++)printf("first")
1赞 Peter Cordes 11/1/2023
是的,就像我说的,这完全等同于 .你要么需要做一些特定于目标的未定义行为的东西(基本上是编写故意通过缓冲区溢出对自己进行 ROP 攻击的代码),要么你需要做更多的打印,或者安排在 main 结束后被调用。或者使用支持并查找和修改该部分中的字符串数据的旧 GCC 版本进行编译。(或者使用较新的 GCC,使用链接器选项或使所有页面都可写。study();atexit(study)study-fwriteable-strings.datamprotect
2赞 John Bollinger 11/1/2023
目前尚不清楚这是什么类型的挑战。您标记了 [buffer-overflow],这表明您需要构建某种漏洞,但您描述的尝试只是一种语法解决方法,以避免直接通过其名称进行调用。是否需要执行?studystudy()

答:

4赞 3 revschux - Reinstate Monica #1

也许不是 OP 所想的,但宏可以解决问题。

#include <stdio.h>

void study() {
  printf("this is the second.\n");
}

void study2() {
  int bold[4];
  // can only modify this section BEGIN
  // Without calling any "call" or "jump" function, 
  #define F1 study
  #define F2 study2
  #define study2() F2(); F1();
  // can only modify this section END
  printf("this is the first\n");
}

int main(int argc, char *argv[]) {
  study2();
  return 0;
}

输出

this is the first
this is the second.

也许违反了<我们也不能称之为“学习”的功能>。取决于“我们”,如果“我们”是代码之间的部分,就可以了。BEGIN ... END

否则,如果在代码中的任何地方,则可能是直接方法:"we"

void study2() {
  int bold[4];
  // can only modify this section BEGIN
  #define printf(x) printf("this is the first\nthis is the second.\n")
  // can only modify this section END
  printf("this is the first\n");
}

@Peter Cordes 提出了第三种方式,我们的修改不调用 .study()

void study2() {
  int bold[4];
  // can only modify this section BEGIN
  atexit(study);
  // can only modify this section END
  printf("this is the first\n");
}

评论

1赞 Peter Cordes 11/1/2023
我喜欢;我们不是从 BEGIN 内部调用的。END 区域。如果根本不被调用,那么它存在于源代码中是没有意义的。(除非他们希望通过覆盖 的返回地址 和 通过未定义行为的地址来调用它,否则在不需要通过多个堆栈插槽对齐堆栈指针的系统上。 例如,一些主流 ISA(如 x86 和 ARM)的典型 32 位调用约定。这些也是指针宽度通常为 的,与 x86-64 不同。study()study()study2study1sizeof(int)
1赞 Peter Cordes 11/1/2023
另一种在不自己打电话的情况下被叫到的方法是 .它当然会在程序执行期间被调用,所以这是“我们”等语义的同一个问题。这样做的一个缺点是没有声明,所以我们依赖于隐式声明与实际原型兼容。(这在大多数主流调用约定中都是如此,ISO C89 允许隐式声明,大多数编译器在以后的模式中也支持它。studyatexit(study)stdio.hatexit()int atexit(void (*function)(void));
0赞 chux - Reinstate Monica 11/1/2023
@PeterCordes 是的,这些类型的可执行文件更改非常依赖于实现,并且应该有一个特定的平台标签。
2赞 Erik Eidt 11/1/2023
哇,太可怕了!爱上它!
1赞 Ted Lyngmo 11/1/2023
当我看到这样的东西时,我会晕船。+1 :-)