代码先锋网 代码片段及技术文章聚合

VNCTF2021-FilpGame

技术标签: 逆向  算法

前言

vnctf2021比赛期间我只看了一下reverse的FilpGame,当时写了脚本跑出来了其中一个答案,但是题目要求答案一定是最短的,正当我脑袋卡住的时候,同学邀我出去玩,比赛就咕咕咕了,现在官方wp出来了,我就回来重新看一下这题

我的思路

先说说我的思路吧,我自己感觉还是很正确的。IDA分析(结合题目FilpGame)发现是个十字翻棋游戏,果断上网找这个游戏的脚本https://blog.csdn.net/gagaprince/article/details/50532055,感觉这个blog的解法完美了,于是问题变成了异或方程组求解,异或方程组用高斯消元,可以看这个博客复习一下https://blog.csdn.net/lzyws739307453/article/details/89816311/

我就开始用c++写解密程序,结果不知道什么原因它一直说无解,于是我决定用python,sagemath来写脚本

c = []
b = [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1,1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1,0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1,0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1]
for i in range(256):
    c.append([0 for i in range(256)])
    if i < 16:
        if i % 16 == 0:
            c[i][i] = 1
            c[i][i+1] = 1
            c[i][i + 16] = 1
        elif i % 16 == 15:
            c[i][i] = 1
            c[i][i - 1] = 1
            c[i][i + 16] = 1
        else:
            c[i][i] = 1
            c[i][i - 1] = 1
            c[i][i + 1] = 1
            c[i][i + 16] = 1
    elif i+16>255:
        if i % 16 == 0:
            c[i][i] = 1
            c[i][i + 1] = 1
            c[i][i - 16] = 1
        elif i % 16 == 15:
            c[i][i] = 1
            c[i][i - 1] = 1
            c[i][i - 16] = 1
        else:
            c[i][i] = 1
            c[i][i - 1] = 1
            c[i][i + 1] = 1
            c[i][i - 16] = 1
    else:
        if i % 16 == 0:
            c[i][i] = 1
            c[i][i + 1] = 1
            c[i][i - 16] = 1
            c[i][i + 16] = 1
        elif i % 16 == 15:
            c[i][i] = 1
            c[i][i - 1] = 1
            c[i][i - 16] = 1
            c[i][i + 16] = 1
        else:
            c[i][i] = 1
            c[i][i - 1] = 1
            c[i][i + 1] = 1
            c[i][i - 16] = 1
            c[i][i + 16] = 1
F = GF(2)
A = Matrix(F, c)
result = A.solve_right(vector(b))
for i in range(len(result)):
    if result[i] == 1:
        print(hex(i)[2:].rjust(2,'0')[::-1].upper(),end = '')
#102050A0F00111314151617191A1B1C1F13242526292B2D2F2032343537383A3B3C3E324346484A4C4F4051555758595A5B5C5E51666C6377787A7C7D7E7F708184868C8D8E8F809192979C9E91A3A4A5A7A8AAABACADAFA0B2B3B7B8B9BABBBCBDBFB0C2C4C5C6CACBCDC0D1D2D3D5D6D7DADBDCDDDED0E2E3E5E6E7EAEBEEEFE0F1F2F7F

得到的结果输入程序发现是wrong,调试一下才发现原来是长度超了,估计是有自由变元,我patch了一下程序限制的长度,验证了我这个答案是正确的,但不是最短的,我翻了翻sagemath的文档,似乎没有写出通解的函数。

至此我就卡住了,我只能自己写一个高斯消元并且穷举,但是我的程序总是不对,最后也没整出来

wp学习

看了作者给出的wp,穷举第一行的可能性,其他行由第一行推出,因为第一行各列之间的影响已经固定,所以第一行只能由第二行影响,第二行可以由第一行推出,以此类推。确实是个可行的办法

下面给出作者的脚本,我按照我的理解加了点注释,官方wp在公众号 白帽100安全攻防实验室

#define MAXN 16
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <vector>
using namespace std;
char a[MAXN][MAXN], g[MAXN][MAXN];
int dx[] = { -1, 0, 0, 0, 1 }, dy[] = { 0, 0, -1, 1, 0 }, ans;
vector < pair<int, int> > PathVec;
vector < pair<int, int> > PathAns;
void filp(int y, int x)
{
	PathVec.push_back(make_pair(x, y));
	ans++;
	for (int i = 0; i < 5; i++)
	{
		int nx = x + dx[i], ny = y + dy[i];
		if (0 <= nx && nx < MAXN && 0 <= ny && ny < MAXN) g[ny][nx] ^= 1;
	}
}

int solve()
{
	int min_Ans = 1000;
	for (int i = 0; i < 1 << 16; i++) //**第一行的所有可能
	{
		PathVec.clear();
		ans = 0;
		memcpy(g, a, sizeof(a));
		for (int j = 0; j < MAXN; j++)
			if (i >> j & 1) filp(0, j);
		for (int j = 0; j < MAXN - 1; j++) //其他行由第一行推出,因为第一行各列之间的影响已经固定,所以第一行只能由第二行影响,第二行可以由第一行推出,以此类推
			for (int k = 0; k < MAXN; k++)
				if (g[j][k] == 0) filp(j + 1, k);
		for (int j = 0; j < MAXN; j++)//最后一行有0则代表这个第一行不行
			if (g[MAXN - 1][j] == 0)
				goto next;
		if (min_Ans > ans)//要翻的次数最少的情况
		{
			PathAns.assign(PathVec.begin(), PathVec.end());
			min_Ans = ans;
		}
	next:;
	}
	if (min_Ans == 1000) min_Ans = -1;
	return min_Ans;
}
char ToChar(int x)
{
	if (x >= 10) return x - 10 + 'A';
	return x + '0';
}
int main()
{
	unsigned short data[MAXN] = { 0xBEF5, 0x8BBC, 0xA0E9, 0x7310, 0xD910, 0xA3AD, 0xCCB6, 0x4DDE, 0x344C, 0x3BD6, 0x6711, 0x868F, 0x1C7A, 0x8425, 0x6B0D, 0x1B4C };
	while (true)
	{
		/*
		memset(data, 0, sizeof(data));
		time_t t;
		srand((unsigned)time(&t));
		for (int i = 0; i < MAXN; i++)
		{
			for (int j = 0; j < MAXN; j++)
				data[i] = data[i] << 1 | (rand() % 2);
		}
		*/
		for (int i = 0; i < MAXN; i++)
			for (int j = 0; j < MAXN; j++)
				a[i][j] = data[i] >> (MAXN - 1 - j) & 1;
		int ans = solve();
		if (ans != -1) break;
	}
	for (auto x : PathAns)
	{
		printf("%c%c", ToChar(x.first), ToChar(x.second));
	}
	printf("\n");
	for (int i = 0; i < MAXN; i++)
	{
		printf("0x%X, ", data[i]);
	}

	return 0;
}

十字翻棋游戏最大的弱点是每一个点只能两种状态:被点击和不被点击,点击两次和不被点击是一样的。我的方法是高斯消元,但是有自由变元,即有多解,理论上来说写出通解穷举一下应该也行,毕竟是个GF(2)。作者的方法是**第一行,由第一行推出其他行,涵盖了所有可能的解,并且**只有2**16,是一个非常好的思路

版权声明:本文为foreve3n1ght原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/foreve3n1ght/article/details/114944213

智能推荐

写给2021

In the first half of 2020, I thought we should return to life with a harmonious attitude. Strike a balance between sheer paranoia and interpersonal harmony. Compromise with reality and the doctrine of...

2021 3.14

学java的第一天。(3.14) 小试身手 结果:你好 java 结果:你好java “ln”有换行的意思。 整数类型 byte: -128~127 short: -32768~32767 int: -2147483648~2147483647 long: -9223372036854775808~9223372036854775807 对于long型的值,若数字大于int...

HDOJ 2021

发工资咯:) HDOJ 2021 解题思路 优先整除100,而后是50、10、5、2、1,最后统计。 AC...

2021 3.15

字符类型 ‘s’表示一个字符,而“s”一个字符串; char x=‘a’ 与char x=97表达的意思相同,即为unicode表上的位置; 结果为:100 结果为:?...

2021 3.16

算数运算符 和为:197.56 差为:106.44 积为:6925.12 商为:0.29973686 其中n1后的f表示赋值的数为float型,不能去,否则报错。 逻辑运算符 &&、&:逻辑与 ||:逻辑或 !:逻辑非 &&与&都表示“逻辑与”,当条件1和条件2都为true时,判断结果才为true, 其中“&...

猜你喜欢

2021 3.17

隐式类型转换 精度从低到高排序:byte<short<int<long<float<double 比如: int a=50; float b=a; b的结果是50.0 byte与float:573.12 byte与int:18150 byte与char:12 double与char:55.48883 显式类型转换 int a=(int)12.33; 结果:45 lon...

2021 3.18

对象的使用(赋值,成员变量和成员方法,关键字this) 结果:zz22 关键字this可以在实例方法中使用,但不能在类方法中使用;this可以使成员变量被成功赋值,而成员变量表示当前对象的成员变量,不是局部变量。...

2021 3.19

复合语句(块语句) 结果:40 false hello java 注意:复合语句为局部变量创建了一个作用域,该作用域为程序的一部分,在该作用域中可以使用,但如果在作用域外使用该变量,则会发生错误。 条件语句(1)if条件语句 结果:a小于b...

IDEA中使用Spark SQL查询Hive

1、使用Maven搭建项目环境,添加Maven依赖 2、复制SPARK_HOME/conf下的hive-site.xml文件放在项目resources下,文件内容如下: 3、运行环境:JDK1.8、Scala2.11.12, 4、代码 按照这个流程走没有遇到坑。...