2017看雪秋季赛 第五题

先贴一发官方题解

https://ctf.pediy.com/game-fight-50.htm

从正向出题人的思路考虑,和逆向者的方向考虑这个题,都挺有意思的


首先照例先运行加上IDA分析,找特征字符串

特征字符串:失败

然后找到了函数:406FC3

很明显,这里是对常量字符串进行处理

这里是一个base64的比较,然后加密解密都尝试下,发现进了死胡同!~

使用jump to xref,只有函数4071FD调用了该函数,IDA中显示是这样的

最明显的就是这个MessageBox,明显是给出验证后的啊~所以这里的406FC3就是验证过程

于是再回去看406FC3函数,有个INT 2D断点,学习一发~~~SEH处理函数

所以这里的patch:把int 0x2D的地方改成jmp 40717D

再用IDA分析就变成了这样,所以:

所以检查函数是40680C,v15表示最后的flag,且最后的长度为12


重头来分析406FC3:先是一个407307函数,里面一大段简单赋值语句,可以简单猜测是全局初始化

里面有6个memset函数,其中的间隔距离需要好好分析下

384,9600,18816,28032,37248,46464形成了一个等差数列,其中公差是9216 = 4 * 576 = 48 * 48

所以!每个memset之后,都是一个48*48的矩阵初始化

所以,接下来还是常规操作,扣数据下来~~~

在这里看到eax是一个堆栈的值,可以从堆栈数据窗口弄出来6个48*48的矩阵,其中会发现:每一行每一列都只有1个1,其余的全是0,所以一定是个初始化的数据值


然后是4084A3函数,把KanXueCrackMe2017与我们的初始化矩阵一起做运算

所以点进去4084A3来跟踪,看到一个40829C,对KanXueCrackMe2017做处理变成了v5,然手用v5对后面进行操作

这个v5一定是一个18进制的字符串~所以可以在40829C返回后,查看下返回的值

所以:40829C函数的功能就是把KanXueCrackMe2017转化为常量字符串EDAHE450C741GH441E11BH84

在做题的时候得到了需要的数据就够了,不需要很清楚的知道40829C是干啥的。因为我们知道了,后一个是理解成18进制字符串的,所以戳开40829C,看到这个

这里和18进制理解方法一样,26+26+10=62,把输入字符串理解成62进制,然后转化成18进制~~~


接下来,就是调用4080DF函数,对矩阵a1进行处理

把字符串EDAHE450C741GH441E11BH84的每一位,分解成x/3,x%3+1的形式,所以可以手算个列表出来,形成了一个映射关系

对应关系
商 余数 字符
0  1  - 0
0  2  - 1
0  3  - 2
1  1  - 3
1  2  - 4
1  3  - 5
2  1  - 6
2  2  - 7
2  3  - 8
3  1  - 9
3  2  - A
3  3  - B
4  1  - C
4  2  - D
4  3  - E
5  1  - F
5  2  - G
5  3  - H

然后,程序调用函数模块40727D对矩阵进行操作

这里这个乘法看起来很奇怪,因为v8是个常数值48且是控制循环次数的变量,v7每次增加也是48

这里就需要回头考虑我们的矩阵了,到底有什么特殊的性质

001000000000000000000000000000000000000000000000
000100000000000000000000000000000000000000000000
000010000000000000000000000000000000000000000000
000001000000000000000000000000000000000000000000
000000100000000000000000000000000000000000000000
000000010000000000000000000000000000000000000000
100000000000000000000000000000000000000000000000
010000000000000000000000000000000000000000000000
000000001000000000000000000000000000000000000000
000000000100000000000000000000000000000000000000
000000000010000000000000000000000000000000000000
000000000001000000000000000000000000000000000000
000000000000000000000010000000000000000000000000
000000000000000000000001000000000000000000000000
000000000000000010000000000000000000000000000000
000000000000000100000000000000000000000000000000
000000000000000000000000001000000000000000000000
000000000000000001000000000000000000000000000000
000000000000000000100000000000000000000000000000
000000000000000000010000000000000000000000000000
000000000000000000001000000000000000000000000000
000000000000000000000100000000000000000000000000
000000000000000000000000100000000000000000000000
000000000000000000000000010000000000000000000000
000000000000000000000000000000000010000000000000
000000000000000000000000000000000001000000000000
000000000000000000000000000000000000100000000000
000000000000000000000000000100000000000000000000
000000000000000000000000000010000000000000000000
000000000000000000000000000001000000000000000000
000000000000000000000000000000100000000000000000
000000000000000000000000000000010000000000000000
000000000000000000000000000000001000000000000000
000000000000000000000000000000000100000000000000
000000000000100000000000000000000000000000000000
000000000000010000000000000000000000000000000000
000000000000001000000000000000000000000000000000
000000000000000000000000000000000000010000000000
000000000000000000000000000000000000001000000000
000000000000000000000000000000000000000100000000
000000000000000000000000000000000000000010000000
000000000000000000000000000000000000000001000000
000000000000000000000000000000000000000000100000
000000000000000000000000000000000000000000010000
000000000000000000000000000000000000000000001000
000000000000000000000000000000000000000000000100
000000000000000000000000000000000000000000000010
000000000000000000000000000000000000000000000001

拿第一个作为样例

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;

char a[100][100];
int num[100][100];
int init[100][100];
int vis[100];


void initial(){
   memset(init, 0, sizeof(init));
   for(int i = 0; i <= 80; i++)
		for(int j = 0; j <= 80; j++)
			init[i][j] = 1;
    memset(vis,0,sizeof(vis));
}

void dfs(int x){
    if (vis[x]){
        puts("");
        return;
    }
    vis[x] = 1;
    int i;
    for(i = 0; i <= 47; i++)
        if (num[x][i] == 1) break;
    printf("-%d",i);
    dfs(i);
}

int main(){
    initial();
    for(int i = 0; i <= 47; i++)
        scanf("%s",a[i]);
    for(int i = 0; i <= 47; i++)
        for(int j = 0; j <= 47; j++)
            num[i][j] = a[i][j] - '0';
    for(int i = 0; i <= 47; i++)
        if (!vis[i]){
            printf("%d",i);
            dfs(i);
        }
    printf("\n");
	return 0;
}
0-2-4-6-0
1-3-5-7-1
8-8
9-9
10-10
11-11
12-22-24-34-12
13-23-25-35-13
14-16-26-36-14
15-15
17-17
18-18
19-19
20-20
21-21
27-27
28-28
29-29
30-30
31-31
32-32
33-33
37-37
38-38
39-39
40-40
41-41
42-42
43-43
44-44
45-45
46-46
47-47

说明矩阵中变化的是(0,2,4,6),(1,3,5,7),(12,22,24,34),(13,23,25,35)

所以!这可以定义为旋转操作,转一次有变化,转四次就复原了(不知道哪些dalao可以猜得到是魔方)

到此可以知道是矩阵乘法让初始数据1*48做数学运算,先用EDAHE450C741GH441E11BH84加密,再用我们的输入解密~

于是,加密操作的顺序过程为:(前一个为商表示使用第几个矩阵,后一个为余数表示几次)

4 3
4 2
3 2
5 3
4 3
1 2
1 3
0 1
4 1
2 2
1 2
0 2
5 2
5 3
1 2
1 2
0 2
4 3
0 2
0 2
3 3
5 3
2 3
1 2

所以我们需要的逆向过程,就是根据矩阵乘法的结合律,一步一步的将其复原回去(即补足四次, 1 2的逆向结果为1 2,2 3的逆向结果为2 1,以此类推)

1 2
2 1
5 1
3 1
0 2
0 2
4 1
0 2
1 2
1 2
5 1
5 2
0 2
1 2
2 2
4 3
0 3
1 1
1 2
4 1
5 1
3 2
4 2
4 1

根据18进制的对应关系表,转化成:46F911C144FG147E234CFADC

说明验证通过了某几部分,算法基本正确,还剩下的是几个细节问题

回想到之前有个长度为12的限制,我们输入的flag明显太长了,需要把字符串缩短

根据矩阵和置换的性质,转一次等于转五次,转四次复原,所以连续的11和44可以删除。中间有个5 1和5 2可以合并成5 3,最后两步操作的4 2和4 1可以合并成4 3。于是,转为字符串:46F9C1H147E25CFAE

这里附上中间计算的py和C代码

def getchar(x):
	if x>=0 and x<=9:
		return chr(x + ord('0'))
	elif x>=10 and x<=35:
		return chr(x - 10 + ord('A'))
	else:
		return chr(x - 36 + ord('a'))

def getint(x):
	if x>='0' and x<='9':
		return ord(x) - ord('0')
	elif x>='A' and x<='Z':
		return ord(x) - ord('A') + 10
	else:
		return ord(x) - ord('a') + 36

def change(s,a,b):
	s = s[::-1]
	number = 0
	for i in s:
		number = number * a + getint(i)
	print number
	flag = ''
	while number > 0:
		left = number % b
		number = number / b
		flag += getchar(left)
	return flag

if __name__ == '__main__':
	s = 'KanXueCrackMe2017'
	ans = change(s,62,18)
	div = []
	mod = []
	print ans
	for i in ans:
		number = getint(i)
		print number / 3, number % 3 + 1
		div.append(number / 3)
		mod.append(number % 3 + 1)
	#print div[::-1],mod[::-1]
	div = div[::-1]
	mod = mod[::-1]
	print '------'
	s = ''
	for i in xrange(0,len(div)):
		print div[i], 4 - mod[i]
		number = 3 * div[i] + 4 - mod[i] - 1
		s += getchar(number)
	#s = 'EDAHE450C741GH441E11BH84'
	#s = s[::-1]
	print s
	#s = '46F911C144FG147E234CFADC'
	#s = '46F9C1FG147E234CFADC'
	s = '46F9C1H147E25CFAE'
	#s = '46F9C1H147E25CFA0'#0,3,6,9,C,F
	ans = change(s,18,62)
	print ans
	'''
	string = ''
	f = open('data.txt','r')
	for line in f:
		print line[-2]
		string += line[-2]
	print string
	print len(string)
	f.close()
	f = open('matrix.txt','w')
	for i in xrange(0,len(string),48):
		f.write(string[i:i+48])
		f.write('\n')
		if (i + 48) % (48 * 48) == 0:
			f.write('\n')
	f.close()
	f = open('matrix1.txt','r')
	number = []
	for line in f:
		num = line.find('1')
		number.append(num)
	print number
	print sorted(number)
	'''

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;

const int maxn = 47;

char a[maxn+10][maxn+10];
int num[maxn+10][maxn+10];
int init[maxn+10][maxn+10],calc[maxn+10][maxn+10];
int T;

void initial(){
	memset(init, 0, sizeof(init));
	memset(calc, 0, sizeof(calc));
	for(int i = 0; i <= maxn+10; i++)
		for(int j = 0; j <= maxn+10; j++)
			init[i][j] = num[i][j];
}

int main(){
	freopen("matrix.txt","r",stdin);
	freopen("stdout.txt","w",stdout);
	for(T = 1; T <= 6; T++){
		for(int i = 0; i <= maxn; i++)
			scanf("%s",a[i]);
		for(int i = 0; i <= maxn; i++)
			for(int j = 0; j <= maxn; j++)
				num[i][j] = a[i][j] - '0';
		initial();
		for(int times = 1; times <= 3; times++){
			for(int i = 0; i <= maxn; i++)
				for(int j = 0; j <= maxn; j++)
					for(int k = 0; k <= maxn; k++)
						calc[i][j] += init[i][k] * num[k][j];
			for(int i = 0; i <= maxn; i++)
				for(int j = 0; j <= maxn; j++)
					init[i][j] = calc[i][j];
			if (times == 3){
				for(int i = 0; i <= maxn; i++)
					for(int j = 0; j <= maxn; j++)
						printf("%d%c",calc[i][j],j==maxn?'\n':' ');
			}
			else{
				for(int i = 0; i <= maxn; i++)
					for(int j = 0; j <= maxn; j++)
						calc[i][j] = 0;
			}
		}
		printf("\n");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值