谷甚论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指针的位置。骗输出同理。使用seekg、seekp,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成功!