谷甚论Hack交互库(下篇)——杂谈

· · 个人记录

谷甚论Hack交互库(下篇)——杂谈

本文是一片杂谈,讲述了一些Hack交互库可以用到的技巧。它们大多不靠谱。

谷甚论Hack交互库(上篇)——搜索关键变量

在程序开始时执行、在程序结束时执行

如果main函数不是你自己实现的,你可能需要对程序的执行顺序做一些手脚。比如在main执行之前/退出之后插入一些代码。

通过全局变量的动态初始化来在main函数之前执行,通过atexit注册在main函数退出之后执行的函数。

#include <cstdio>
#include <cstdlib>
int func1() {
    puts("func1");
    return 0;
}
void func2() {
    puts("func2");
}
int a = func1();
int main() {
    atexit(func2); // 定义于 <cstdlib>
    puts("main");
    return 0;
}

输出:

func1
main
func2

骗输入输出

如果你知道明确的输入输出格式,你可以绕过交互库自己输入输出。然而,大部分交互库都会加密输入输出格式/输出Token什么的,这个方法瞬间暴毙。仅用于那些没有防御的naive交互库。

例如,这样的代码曾经可以通过P1947(现在不行了):

#include <cstdio>
#include <iostream>
extern "C" int Seniorious(int);
int getAns() {
    int a, b, c;
    std::cin >> a >> b >> c;
    std::cin.seekg(0);
    return c;
}
int ans = getAns();
extern "C" int Chtholly(int n,int OvO) {
    return ans;
}

骗输出也是一样,如果交互库检查完你的答案正确后输出一个Correct,那你不用他的自己输出个不就完了么(

注意,如果你想让你读入之后回到交互库还能正常读入,你需要重置IO指针的位置。骗输出同理。使用seekgseekp,C式IO的fseek或者类似的东西。

std::cin >> a >> b >> c;
std::cin.seekg(0); // 输入流回到文件的开头
......
std::cin >> a >> b >> c; // 重新读入,还是刚才的a,b,c

另外,还有一个函数setbuf用来设置流缓冲区的位置。你可以把输出缓冲重定向到你自己的数组里,然后等交互库输出完对它做一些处理什么的。

RNG武器

如果交互库使用了rand这种低端随机数生成器,你也许能在交互的过程中srand来操纵交互库出的随机数。例子

关于上篇中提到的捕获段错误

想了一下觉得还是讲一下比较好。这部分内容比较难表述,还是用代码讲清晰。

#include <csetjmp>
#include <csignal>
jmp_buf jump_buffer;
void handle(int x) {
    longjmp(jump_buffer, 1);
}
int __x = -1;
int main() {
    signal(SIGSEGV, handle); // 定义于 <csignal>
    // 用于注册处理信号的函数。SIGSEGV是代表非法内存访问(段错误)的信号。
    char *i = (char*) &__x;
    int r = setjmp(jump_buffer); // 定义于 <csetjmp>
    // 保存当前的运行环境。
    if (r == 0) {
        for (; ; i++) {
            char c = *i; // 无限扫描内存
            ......

执行这段代码时,第一次执行到setjmp时会返回0,由此进入下面的扫描内存的代码块。

当扫描到非法内存,出现段错误时,正常情况下会直接RE崩溃的,但是我们已经注册了函数,于是跳转到handle中。然后调用longjmp函数跳转回我们之前保存的运行环境。

这个时候代码会跳转到setjmp的地方重新运行!其效果就好像回到了setjmp刚刚返回的时候一样。但是这次这个函数会返回1(其实就是longjmp里传入的第二个参数),不会执行到下面扫描内存的代码。程序可以继续运行下去,并没有崩溃。

使用这种方法,你可以保证扫描了所有合法的全局内存段,直到出现段错误为止,又不会因为非法内存访问而造成程序崩溃,帮助你更容易地找到关键变量。

猜变量相对位置(很不靠谱)

这个技巧限于搜索关键变量时,对全局变量有用。

搜索关键变量的时候,想要的都没有明显特征?有明显特征的都没用?

如果是全局变量的话,大胆猜想它们地址之间的偏移值!(就是两个地址的差)例如,还是P1947的例子,我们找到了cnt之后,猜测cnt下一个内存位置就是k,交上去,过了!AC代码

——你这也太不靠谱了,就靠瞎猜,不会比直接猜输出文件靠谱吧?
——没错,就是这么不靠谱。

靠谱一些的方法也是有的。可以找一个和评测机相同或者相似的编译环境,写一个程序输出两个地址之间的偏移值。可以认为当程序架构变化不大时这个偏移值不会发生变化(这一点也没有保证,就是玄学),就可以用这个偏移值在提交的程序中进行运算找到变量。当然,也可以记绝对地址(更加更加不靠谱)。

mcfx的最新成果

大致思路是模拟指令集的运行,同时记录下所有交互库造成的内存变动,然后进行处理,比如改回去。

这个太牛逼了,我也不会搞,有需要可以自己去他的博客看

https://mcfxmcfx.blog.uoj.ac/blog/6519

其他

暂时就想到这些,以后想到啥再补吧(

另:祝大家早日Hack成功!