menu 牢记自己是菜
高校战“疫”网络安全分享赛 RE--Rubik
1122 浏览 | 2020-03-13 | 阅读时间: 约 4 分钟 | 分类: 高校战“疫”网络安全分享赛 | 标签:
请注意,本文编写于 1503 天前,最后修改于 1503 天前,其中某些信息可能已经过时。

0x1 题目分析

题目二进制文件,靶机已经关闭
这道题比赛的时候,直接就把我劝退了。先百度一下Rubik,哦,是一个建筑学家,还是魔方之父(这点当时我可没反应上来)。拖入IDA,能找到main函数与一些关键的字符串,但是我压根就没看懂他到底在干嘛,有些函数套在一起,相互间一直调用,而且没找到什么关键信息。然后就没有然后了。。。。
直到昨天认真再看大佬的题解的时候,才慢慢对这道题进行了摸索。大佬指出这是一道拼魔方的题目,Rubik提示魔方的方向。其次,可以使用gdb调试一下,看起来比IDA舒服一些。直到这里,我才看见这道题的真实面目。

题目会给你串长度为9个字节的十六进制数,然后获取你的一个输入。

0x2 数据分析

整道题目的交互很简单,而且在IDA中也没留下很明显的逻辑代码。每次启动GDB,都会获得一串不同的16进制数字,初步猜测这串数字应该是随机生成的。结合题目所给提示,猜测整道题应该需要你输入一些东西,进入程序,完成某些功能,最终给你返回一个flag(逆向题目给了靶机,说明flag应该存储在靶机上,需要你触发某个开关,才可以返回flag)。搜寻IDA发现一组字符。

RUF是一组魔方中经常使用的几个符号,都是英语单词的缩写,意思分别为:

  • U代表under,意思是下面,魔方的底面。U-up 即顶层按顺时针旋转。
  • R代表right,意思是右面,魔方的右侧面。R-right
  • 最右边顺时针旋转。 F代表front,意思是前面,使用者正对着的那个面。F-front 最前一面顺时针旋转。
    到这里基本上可以确定,16进制数是魔方的一种随机状态,而我们需要输入RUF顺序的序列,还原这个魔方,程序负责录入我们输入的序列,进行相应的操作,最终与还原后的魔方进行对比,若可以匹配则返回flag。那么现在最关键的问题就是,将那串随机的16机制数与状态相对应。

分析一波,魔方一般来说是有六个面,而计算机存储也一定不是拿颜色存储的,所以只需要给它6个可分辨的标志位,就可以让他区分6种不同的颜色。设想一下,如果是你在编程最简单的一定是使用编号0-5进行每一个面的代替,用三位二进制数表示6种状态(最多8种)。而十六进制与二进制进行转化是十分方便的,先做一下尝试,直接转换为二进制,每三位为一组,一眼望过去,用很多000,明显不是这样构成魔方的。继续思考,我们拿到的是九个字节的数据每个字节对应8位二进制数字,98/3=24,24/6=4。所以我们拿到的应该是一个22得魔方,这次使用每位对应四位二进制数展开,三三分组。转换标号正好得到了如下数据:

如倒数后三行所示,正好6种数字,每种占4个。说明我们的分析是没有问题的。
于是我们遇到了第二个问题,以何种方式将每种颜色填入我们得魔方当中。这个地方可谓是相当的艰辛,Gdb逐句调试分析逻辑,但是奈何我动态分析真的能力有限,这里就不赘述了。主要还是结合了一下学长给的WP里面的脚本才勉勉强强写出来一个逻辑。

这个地方属实没有太搞清楚,在后面我会贴上学长的脚本。

#coding=utf-8
class Block():
    def __init__(self, bits):
        self.bits = bits
        self.color = self.bits_to_num(bits)
    def bits_to_num(self, bits):
        a = 0
        for i in range(3):
            a *= 2
            a += bits[2 - i]
        return a
def __str__(self):
    ret = ''
    if self.color == 6:
        ret = 'g'
    elif self.color == 5:
        ret = 'w'
    elif self.color == 4:
        ret = 'o'
    elif self.color == 2:
        ret = 'y'
    elif self.color == 1:
        ret = 'b'    
    elif self.color == 0:
        ret = 'r'
    return ret
def __eq__(self, a):
    if self.color == a.color:
        return True
    else:
        return False
def __ne__(self, a):
    if self.color == a.color:
        return False
    else:
        return True
class Rubic():
    def __init__(self, blocks):
        self.blocks = blocks
        self.b1 = blocks[0:4]
        self.b2 = blocks[4:8]
        self.b3 = blocks[8:12]
        self.b4 = blocks[12:16]
        self.b5 = blocks[16:20]
        self.b6 = blocks[20:24]
def get_num(self):
    n = 0
    for i in range(len(self.blocks)):
        for j in range(len(self.blocks[i].bits)):
            n *= 2
            n += self.blocks[i].bits[j]
        return n
def F(self):
    tmp = self.b6[3]
    for i in range(3):
        self.b6[-1 + i] = self.b6[i]
        self.b6[2] = tmp
        tmp1 = self.b5[1]
        tmp2 = self.b5[2]
        self.b5[1] = self.b4[0]
        self.b5[2] = self.b4[1]
        self.b4[0] = self.b2[0]
        self.b4[1] = self.b2[1]
        self.b2[1] = self.b1[2]
        self.b2[0] = self.b1[1]
        self.b1[2] = tmp2
        self.b1[1] = tmp1
def U(self):
    tmp = self.b4[3]
    for i in range(3):
        self.b4[-1 + i] = self.b4[i]
    self.b4[2] = tmp
    tmp1 = self.b5[0]
    tmp2 = self.b5[1]
    self.b5[1] = self.b3[1]
    self.b5[0] = self.b3[0]
    self.b3[1] = self.b2[2]
    self.b3[0] = self.b2[1]
    self.b2[2] = self.b6[2]
    self.b2[1] = self.b6[1]
    self.b6[2] = tmp2
    self.b6[1] = tmp1
def R(self):
    tmp = self.b5[3]
    for i in range(4):
        self.b5[-1 + i] = self.b5[i]
    self.b5[2] = tmp
    tmp1 = self.b6[0]
    tmp2 = self.b6[1]
    self.b6[1] = self.b1[1]
    self.b6[0] = self.b1[0]
    self.b1[1] = self.b3[2]
    self.b1[0] = self.b3[1]
    self.b3[2] = self.b4[2]
    self.b3[1] = self.b4[1]
    self.b4[2] = tmp2
    self.b4[1] = tmp1
def __str__(self):
    ret = ''
    ret += '    '
    ret += self.b4[3].__str__() + self.b4[2].__str__() + '\n'
    ret += '    '
    ret += self.b4[0].__str__() + self.b4[1].__str__() + '\n'
    ret += ' '
    ret += self.b2[2].__str__() + self.b2[1].__str__() + ' '
    ret += self.b6[2].__str__() + self.b6[1].__str__() + ' '
    ret += self.b5[1].__str__() + self.b5[0].__str__() + ' '
    ret += self.b3[1].__str__() + self.b3[0].__str__() + '\n'
    ret += ' '
    ret += self.b2[3].__str__() + self.b2[0].__str__() + ' '
    ret += self.b6[3].__str__() + self.b6[0].__str__() + ' '
    ret += self.b5[2].__str__() + self.b5[3].__str__() + ' '
    ret += self.b3[2].__str__() + self.b3[3].__str__() + '\n'
    ret += '    '
    ret += self.b1[2].__str__() + self.b1[1].__str__() + '\n'
    ret += '    '
    ret += self.b1[3].__str__() + self.b1[0].__str__() + '\n'
    return ret

if __name__ == '__main__':
    x = int(input(), 16)
    b = [0 for _ in range(72)]
        idx = 0
    while x != 0:
        b[71 - idx] = x & 1
        idx += 1
        x >>= 1
    blocks = []
    bit = []
    for i in range(len(b)):
        if i % 3 == 0 and i != 0:
            blocks.append(Block(bit))
            bit = []
        bit.append(b[i])
    blocks.append(Block(bit))
    r = Rubic(blocks)
   print(r)

这个地方先留做疑惑,等一波其他大佬的解释,到时候深度学习一下,如何将这个还原逻辑从汇编中找出来。只要找到这个逻辑,这道题就已经结束了。附上一个在线解密魔方的网站,输入解密完成。将代码串check一下,得到flag。

0x3 完整操作

打开GDB,获取随机16进制字符串(靶机已经关闭了),分析字符串,构成魔方

在线运行,返回GDB输入答案,cat在文件中我自己创建的假flag。


0x4 总结

总体来说,这次的魔方题算是一次开眼界吧。让我知道在逆向里除了代码分析脱壳以外还有这种题型。这里的汇编还是我的一大障碍,还是要多读,多练。还有就是大佬写的题解是真的简略QAQ,再等一等,要是之后有大佬把中间分析过程写出来,我在好好学习一下,到时候再继续完善这篇博客。

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!