这题最终有5个队伍解出来,我比赛的时候感觉不算很难,不过比赛的那两天正好有事,大致看了下觉得有希望,但是没什么时间所以没做出来。然后过了几天再做,没想到花了近一周的时间才搞定,确实超出我的能力了。
题目给了一个附件和提示:https://en.m.wikipedia.org/wiki/Treap
看了这个,知道了Treap就是二叉树和堆的结合体,和平衡二叉树有点类似。
代码基本是全汇编写的,采用代码片段实现业务逻辑的单条指令的执行
这里是2个代码片段,实际实现的代码就这两句:
lea rcx,[rsp+40h]
inc eax
按照官方的wp,这里的垃圾代码应该用IDA直接patch掉,变成跳转语句,这样可以用IDA直接显示业务逻辑,我当时没有patch,是用了个笨办法,把指令提取出来,然后输出到文件里,然后对这些垃圾指令进行过滤,然后得到比较清晰的汇编代码,过滤的操作代码是这样的
with open(r'g:\share\20220226\tttree\tree.log','r') as f:
g=open(r'g:\share\20220226\tttree\tree1.log','w')
lines=f.readlines()
frame=0
skip=0
for l in range(len(lines)):
line=lines[l].split()
if line[2]=="retn":
frame+=1
skip=0
elif line[2]=="push" and line[3]=="rax" and "pushfq" in lines[l+1]:
skip=1
elif line[2]=="popfq":
skip=0
elif skip==0:
g.write(lines[l])
g.close()
with open(r'g:\share\20220226\tttree\tree1.log','r') as f:
g=open(r'g:\share\20220226\tttree\tree2.log','w')
lines=f.readlines()
frame=0
skip=0
for l in range(len(lines)-1):
line=lines[l].split()
line2=lines[l+1].split()
if line[2]=="push" and line[3]==line2[3] and line2[2]=="pop":
skip=1
elif skip==0:
g.write(lines[l])
else:
skip=0
g.close()
得到的汇编代码是这样的:
根据这些汇编代码,结合Treap的文档,经过大量的脑力活动,得到代码的逻辑如下:
首先根据输入的字符串,判断前后缀是不是SUSCTF{},然后用中间的32个字符生成Treap树,每个插入的节点权重是伪随机序列生成的随机数,每个节点的值是伪随机数+对应的字符ASCII码,根节点的权重最小。
a=[0x0A2, 0x0AF, 0x09D, 0x0B7, 0x0D2, 0x0CB, 0x0C7, 0x0C6, 0x0B0, 0x0D5, 0x0DA, 0x0E3, 0x0E6, 0x0E8, 0x0E9, 0x0F3, 0x0F4, 0x0EF, 0x0EE, 0x0F7, 0x0F9, 0x0FF, 0x101, 0x0F5, 0x109, 0x11F, 0x11A, 0x146, 0x124, 0x10F, 0x106, 0x0DF]
b=[0x0A8, 0x131, 0x113, 0x047, 0x09E, 0x03B, 0x03A, 0x0BF, 0x092, 0x0F0, 0x174, 0x0C3, 0x289, 0x104, 0x260, 0x04D, 0x2FB, 0x09E, 0x191, 0x158, 0x07D, 0x04A, 0x1E9, 0x101, 0x0D0, 0x0FC, 0x070, 0x11F, 0x345, 0x162, 0x2A4, 0x092]
c=[0x0AC, 0x0FD, 0x247, 0x115, 0x0D4, 0x2B5, 0x1FC, 0x28B, 0x14A, 0x04C, 0x08E, 0x0E9, 0x055, 0x12C, 0x0F5, 0x0E3, 0x081, 0x2E2, 0x1A8, 0x117, 0x152, 0x101, 0x03A, 0x1D0, 0x0A8, 0x0CC, 0x149, 0x137, 0x300, 0x1EC, 0x276, 0x247]
addkey=[0xC1,0xA7,0xC3,0x64,0x77,0x7A,0x7F,0x73,0xAE,0xB2,0x66,0x86,0x99,0x90,0x75,0xB0,0xBC,0x8E,0xAD,0x87,0xBB,0x70,0x65,0x68,0xC0,0x7E,0xA9,0x80,0xC8,0xA5,0x9C,0x98]
最后程序验证这个树是否满足下面的条件:
判断规则:
1、所有子节点,从左->右开始遍历,每个节点的值可以和a列表对应上
2、如果第i个节点是其父节点的左节点,则第i+1个b列表的值为父节点的输入位置×23+父节点的输入字符ASCII
3、如果第i个节点是其父节点的右节点,则第i+1个c列表的值为父节点的输入位置×23+父节点的输入字符ASCII
根据这些规则首先把a列表还原成目标树,我一开始的时候完全是手工完成的,后来看了官方的wp才会根据数据所在的位置来判断树的形状
然后得到树结构以后,再根据列表b和c,从根节点开始逐个生成子节点,直至还原所有的字符。
完整的解题代码如下:
class TreapNode(object):
def __init__(self, value):
self.value=value
self.left = None
self.right = None
self.p = None
self.weight=None
self.slot=None
self.ans=None
def create_tree(inorder,postorder):
if len(postorder)==0:
return None
x=postorder[-1]
pos=inorder.index(x)
root=TreapNode(x)
l=len(postorder)
#print(len(inorder),inorder,postorder,pos,l)
root.left=create_tree(inorder[:pos],postorder[:pos])
root.right=create_tree(inorder[pos+1:],postorder[pos:l-1])
return root
flag=[' ']*32
def walk(node):
global flag
flag[node.slot]=node.ans
if node.left:
i=(b[a.index(node.value)]-ord(node.ans))
if i%23>0:
print(hex(node.left.value),a.index(node.value),node.ans,i,'error')
return
i=i//23-1
node.left.weight=rands[i]
node.left.slot=i
node.left.ans=chr(node.left.value-addkey[i])
walk(node.left)
if node.right:
i=(c[a.index(node.value)]-ord(node.ans))
if i%23>0:
print(hex(node.right.value),a.index(node.value),node.ans,i,'error')
return
i=i//23-1
node.right.weight=rands[i]
node.right.slot=i
node.right.ans=chr(node.right.value-addkey[i])
walk(node.right)
root=create_tree(d,a)
root.weight=min(rands)
root.slot=rands.index(root.weight)
root.ans=chr(root.value-addkey[root.slot])
walk(root)
print(''.join(flag))