高校战“疫”网络安全分享赛Reverse-cycle graph

题目附件:https://adworld.xctf.org.cn/media/uploads/task/7dd5ec756f774898ab3da03ded0fc092.zip

下载下来解压就一个exe,运行之,看不出是什么类型的题。
高校战“疫”网络安全分享赛Reverse-cycle graph_第1张图片
拖进IDA,通过字符串找到程序主要部分
高校战“疫”网络安全分享赛Reverse-cycle graph_第2张图片
F5,看到输入函数之前有一些操作,但和输入没关系不影响,之后用动态调试看就行了
高校战“疫”网络安全分享赛Reverse-cycle graph_第3张图片
后面的检查输入flag的部分看不大懂,不过知道了flag的格式和长度
高校战“疫”网络安全分享赛Reverse-cycle graph_第4张图片
可以注意到v5、v7是两个关键的变量,输入的flag正是与它们进行运算,有必要知道它们运行时的值,先看地址0x403374位置的值。
高校战“疫”网络安全分享赛Reverse-cycle graph_第5张图片
拖入OllyDbg,同样从查找字符串可以找到输入函数的位置,这里可以看出程序加了地址随机化,但偏移是固定的。在这里偏移是0x980000,刚刚所说的位置就是0xD83374。下断点在输入,让程序完成初始化。
高校战“疫”网络安全分享赛Reverse-cycle graph_第6张图片
看数据窗口里的数据,说实话这里是有点靠直觉的,因为题目说了涉及到一些数据结构的知识,主要是图算法,而且这里的数据一看就是小端序的各种地址,所以可以猜到这块区域储存的是组成图的结构体,而且这个图的储存方式是链表储存
高校战“疫”网络安全分享赛Reverse-cycle graph_第7张图片
这些储存地址的单元(就是指针)出现周期是12个字节,而且一次出现两个,不是指针的那个部分的值很小,估计是编号,所以可以推得结构体由两个指针和一个DWORD类型的变量组成

现在问题是,这个结构体存储顺序究竟是变量-指针-指针还是指针-变量-指针还是指针-指针-变量呢?

答案接下来会确定,但这里至少可以确定不是指针-指针-变量,因为第一个结构体的变量在0xD83374,它的前两个DWORD都是0,如果这两个是指针,那么这个结点就是孤立的结点,在这里就没有意义了。
高校战“疫”网络安全分享赛Reverse-cycle graph_第8张图片
再回到IDA来看检查部分第一个循环,v5的初值就是第一个结点的编号,那么一般情况下会认为这里v4的初值dword_403370和v7的初值dword_403378是第一个结点的两个指针,结构体的存储顺序就应该是指针-变量-指针,但其实不是,因为看后面这个v4所有的变化只有自增,指针是不可能自增的,所以这个v4没有任何实质作用,纯粹是个坑。。
高校战“疫”网络安全分享赛Reverse-cycle graph_第9张图片
由此其实就确定了结构体储存顺序是变量-指针-指针。用C/C++来写就是

struct node{
	int data;
	node* lptr;
	node* rptr;
	};

图中v7的初值就是第一个结点的左指针了。v11则是输入的字符串“{}”中的各字符的遍历。
两个if语句的条件都是用v7指向的位置的值即指针所指向的结构体的data的值和v5进行运算,然后与v11比较。这些可以在exp中照抄。if中的语句,v7=*(_DWORD *)(v7+4)v7=*(_DWORD *)(v7+8)实际上是把v7指向的结点的第二个成员和第三个成员即左指针和右指针赋值给v7。用C/C++就是

v7=v7->lptr;
v7=v7->rptr;

在循环中,输入字符串的每个字符必须满足v7 + v5 == v11v5 - v7 == v11两个条件之一,而v7则会变化到与它相邻的结点,这就是在图中的移动。由后面可以知道终点是unk_4034F4指向的结点,而且在OllyDbg中可以看到这个结点的两个指针都指向自己。

高校战“疫”网络安全分享赛Reverse-cycle graph_第10张图片
高校战“疫”网络安全分享赛Reverse-cycle graph_第11张图片
有这些信息就可以写exp模拟在图上的遍历过程了。目标就是在这张图中以16步(因为循环只有16次,flag括号中的部分长度也只有16)从起点结点走到终点结点。这里有一个需要注意的地方就是这个图不是有向无环图,一开始我就简单写了一个dfs,结果跑不出来,加了一个标记变量才出来。
exp源代码如下:

#include
#include
using namespace std;
struct node{
	char data;
	node *lptr;
	node *rptr;
	bool mark=false;			//标记结点是否走过
}g[33];
char flag[17];					//存储flag。flag[0]是起点结点的data值
bool isvisi(char c){
	return (c>='0'&&c<='9')||(c>='a'&&c<='z')||(c>='A'&&c<='Z');		//判断是否是可视字符,因为flag中不可能有不可视字符,不过这里没有考虑各种标点符号以及下划线
}
bool dfs(node *p,int i){
	//p是当前结点指针,i是步数
	if (i==17&&(p==g[32].lptr)) return true;		//走到终点
	else if (p->mark) return false;
	p->mark=true;
	if (isvisi(flag[i-1]+p->data)){					//对应v7+v5==v11
		flag[i]=flag[i-1]+p->data;
	 if (dfs(p->lptr,i+1)) return true;
	}
	flag[i]=flag[i-1]-p->data;						//对应v5-v7==v11
	if (dfs(p->rptr,i+1)) return true;
	else{
		p->mark=false;
		return false;
	}
}
int main()
{
	ifstream fin;
	int n;
	fin.open("cyclegra_00593000.mem",ios::binary);	//这个文件是OllyDbg中导出的数据文件
	fin.seekg(0x374);
	for (int i=0;i<33;i++){
		fin.read((char*)&g[i].data,4);
		int l,r;
		fin.read((char*)&l,4);
		fin.read((char*)&r,4); 
		g[i].lptr=l==0?NULL:g+(l-0x593374)/12;			//读入各结点并把指针的地址转换成数组中对应的地址,这个地方减0x593374是我当时程序在内存中的地址
		g[i].rptr=r==0?NULL:g+(r-0x593374)/12;
	}
	fin.close();
	node *p;
	p=g->lptr;							//v7的初始值
	flag[0]=g[0].data;					//v5的初始值
	dfs(p,1);
	cout<<flag+1;
	return 0;
}

运行结果前16个字符即flag
高校战“疫”网络安全分享赛Reverse-cycle graph_第12张图片

你可能感兴趣的:(CTF)