TCTF2020 Happy_Tree WP

首先得说,不愧是TCTF,这题目的难度令人害怕。
直接进入正题,IDA打开,发现程序函数并不多,但是有一个函数巨大无比,可以看出调用了大量的malloc函数,可以根据题目名称来猜测出这是个BuildTree的过程
索性就不分析了,基本上分析了也没有意义,可以从树的定义来入手,弄清楚树节点的每个域的作用,找到main函数,发现将分配的那一大堆数据地址传入了一个函数,查看函数结构,我们可以推断出一个节点肯定要保存子节点,和子节点的数目,所以可以重新定义结构命名变量
目前来说只能知道SonNum,SonNodeArray,和一个奇怪的Function,而arg1和arg2不知道有啥用先不分析,继续分析函数。
然后根据交叉应用找到了一个奇怪的数组,里面保存了scanf数据,一些函数,一些常量以及预留的一些运行时变量

所以我们可以想到,这棵树用一种遍历树的方式来实现了程序的流程,接下来就可以发现一些奇怪的函数,当修改传入的参数类型后就可以发现这些函数的作用
比如这个CallNode,他这个函数的操作是判断节点的儿子树,然后做出一些调用函数的行为:取第一个儿子的函数计算出的返回值,然后将接下来的儿子函数返回值当作参数来调用,根据前面的Const数组我们可以知道scanf时怎么被调用的了,必定第一个儿子的函数返回了Const数组里的scanf地址,然后其他儿子返回了Const数组里的一些参数,实现了调用scanf函数的操作,果不其然找到了那个函数

所以,至少知道程序在干啥了,这树就相当于是控制程序流程的,每个节点都有一个函数和两个附加参数,这个节点函数会根据两个附加参数和子节点函数返回值做出一系列的操作从而实现程序的流程。

然后继续可以分析出每一个节点函数所代表的意思,这里面包括了选择控制函数,循环控制函数,计算函数等等,基本十分健全,组成了程序的基本要素。
比如:
1.GetConst的两个函数是取Const数组值,参数从节点arg里取,
2.GetArg1就是直接返回Arg1的值,
3.DoAllNodes系列就是遍历所有的节点执行一遍返回最后一个节点函数返回值,
4.DoFirstNode系列就只执行第一个节点函数,
5.AddNode0_1_or_4_node1函数其实就是取数组的值,第一个节点返回数组地址,第二个节点函数返回要读取第几个值,而两个参数决定了是取Byte还是Dword,
6.WhileNode2_DoNode3_4其实就是个for循环,各个节点函数有不同的作用,
7.SelectNode1_2By0就是根据第一个节点返回值==0来判断是调用节点2还是3的函数.
所以基本就是这些,接下来要想办法把树的结构给弄出来,给可视化,不然难以逆向。
运行时观察到发现,节点数据第一个四字节代表arg1的值,第二个代表arg2的值,第三个代表节点函数的地址,第四个代表儿子的个数,第五个代表指向儿子节点地址数组的指针,根据TreeLib我们可以写出如下idapython代码,提取出树的结构

from idc import *
from idautils import *
from idaapi import *
from treelib import Node, Tree
import treelib
start=0x566551D0 #Chunk Start Position
nodeList=[]
tree=Tree()
#file=open("ops","w")
def AddList(node):
	arg1=Dword(node)
	arg2=Dword(node+4)
	Sub=Dword(node+8)
	#file.write(GetFunctionName(Sub)+"\n")
	SonNum=Dword(node+12)
	nodeList.append((arg1,arg2,Sub,SonNum))
def GetName(id):
	strx="node"+str(id)
	return strx
def GetData(node,idx):
	arg1=Dword(node)
	arg2=Dword(node+4)
	Sub=Dword(node+8)
	return ("%s(%s,%s) - index: %d" % (GetFunctionName(Sub),str(arg1),str(arg2),idx))
idx=0
def WalkTree(node):
	global idx
	SonNum=Dword(node+12)
	if SonNum==0:
		return
	SonArr=Dword(node+16)
	i=0
	fa=idx
	while i<SonNum:
		Son=Dword(SonArr+i*4)
		AddList(Son)
		idx+=1
		tree.create_node(GetData(Son,idx),GetName(idx),parent=GetName(fa))
		WalkTree(Son)
		i+=1

AddList(start)
tree.create_node(GetData(start,0),"node0")
WalkTree(start)

然后就得到了令人裂开的树图
没办法只能一点点分析,可以分析出有个循环,然后执行了位运算的操作
其中Const[7]保存的其实是输入的第n个四字节,flag长36字节,所以有九组
最后的操作就是

t=(((t^(t<<13))>>17)^(t^(t<<13)))^((((t^(t<<13))>>17)^(t^(t<<13)))<<5)

上述语句循环100000次,然后最后有一个比较,
Calc的0代表的就是比较,然后和一大个子树算出的常数进行比较,所以我们只需要在此处下断点就可以得到四字节了,然后分析树的其他地方,发现操作时差不多的,有的完全一样,有的则异或了一个奇怪的常数再这样运算最后比较的,所以逻辑就差不多了:
1.我们先提取出比较的数据
2.提取出异或的数据(如果有),这个可以通过输入已知明文dump出程序密文,然后异或直接加密的结果算出异或的常数
3.逆向算法

接下来就是逆向这个循环100000次的位运算,想要爆破是不存在的,基本破解不出来,可以发现这个语句其实是

a^(a<<5),a^(a>>17),a^(a<<13)

的嵌套,所以只要知道一个怎么逆向就知道其他怎么算了。
这里选a^(a<<5)的逆向
把比特位画出来

可以发现,第六位和原来的第一位进行了异或操作,所以说只要a[i]^=a[i-5],每一位都这样操作就可以算出原来的四字节了。
所以可以很容易的写出逆向代码

#include<cstdio>
#include<bitset>
using namespace std;
unsigned int rev_l5(unsigned int x)
{
	bitset<32> bit(x);
	for(int i=5;i<32;i++)
	{
		if(bit[i]==bit[i-5])
			bit[i]=0;
		else
			bit[i]=1;
	}
	return bit.to_ulong();
}
unsigned int rev_l13(unsigned int x)
{
	bitset<32> bit(x);
	for(int i=13;i<32;i++)
	{
		if(bit[i]==bit[i-13])
			bit[i]=0;
		else
			bit[i]=1;
	}
	return bit.to_ulong();
}
unsigned int rev_r17(unsigned int x)
{
	bitset<32> bit(x);
	for(int i=14;i>=0;i--)
	{
		if(bit[i]==bit[i+17])
			bit[i]=0;
		else
			bit[i]=1;
	}
	return bit.to_ulong();
}
unsigned int rev(unsigned int xx)
{
	unsigned int x=xx;
	for(int i=0;i<100000;i++)
	{
		x=rev_l5(x);
		x=rev_r17(x);
		x=rev_l13(x);
	}
	return x;
}

然后将dump的数据,异或的数据,拿来逆向求解就可以得到flag了
flag: flag{HEY!Lumpy!!W@tcH_0ut_My_TrEe!!}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值