首页
社区
课程
招聘
[原创]2018CTF团队赛 第十一题 伊甸园
2018-12-23 00:27 5918

[原创]2018CTF团队赛 第十一题 伊甸园

2018-12-23 00:27
5918

程序流程

用 IDA 分析程序,流程如下
全局类初始化表部分:

  1. 以 10000 字节为分割初始化了7段表达式,最后汇成最终表达式 F
  2. 初始化三段字符串 enc0(1000) enc1(8000) enc2(8335)

主函数部分:

  1. 读入输入 ipt,合成 enc = enc0 + ipt + enc1 + enc2,校验长度 17415
  2. 用 Base62[^1x] 解码,得到表达式 f,校验长度 12715
  3. 解析 f,得到由 CTreeNode 组成的 tree(PS:后文有类似解析代码)
  4. 执行了某种运算,再准备硬啃时,经过队友 新手慢慢来 提点,推断出应该是 微分(求导)、化简 过程
    聊天记录
  5. 将微分结果输出成前缀表达式 f_,与 F 对比
  6. 用 StrCheck 校验 enc(PS:不影响解题,可能是为了确保唯一解加的)

长度分析:

  1. enc0 + enc1 + enc2 长度 17335,所以 ipt 长度 17415 - 17335 = 80
  2. enc0 直接解 b62 会报 pad 错误,多余 2 字节,令 dec0 = b62dec(enc0[:-2]),长度 726
  3. 根据多余的 2 字节分析,接下来应该是 ',x,' 或 ',pi,'
  4. enc1_2 多余第一字节,令 dec2 = b62dec(enc1_2[1:]),长度 11929
  5. 根据多余的 1 字节分析,前面是 ',',从内容看也是
  6. 所以剩下 12715 - 726 - 11929 = 60,令这一部分为 code,其以 ',x,' 或 ',pi,' 开头,以 ',' 结尾

[^1x]: Base62:网上没有标准,是一种为了 URL 而存在的 Base64 改进型,替换了 URL 敏感的 '+' '/' 符号,本实现具体为 '+'->'9B' '/'->'9C' '='->'9D' '9'->'9A'

前缀解析

先上解析代码

with open("yidian.exe", "rb") as f:
    f.seek(0x3cc18)
    func = f.read(10000)
    f.seek(0x3f330)
    func += f.read(10000)
    f.seek(0x41A60)
    func += f.read(10000)
    f.seek(0x44188)
    func += f.read(10000)
    f.seek(0x468a8)
    func += f.read(10000)
    f.seek(0x48fc8)
    func += f.read(10000)
    f.seek(0x4b6e0)
    func += f.read(4875)

    f.seek(0x4c9f0)
    enc0 = f.read(1000)
    f.seek(0x4cdf8)
    enc1_2 = f.read(8000) # 组合 enc1 + enc2
    f.seek(0x4ed40)
    enc1_2 += f.read(8335)

class Node:
    def __init__(self, s):
        self.left = self.right = None
        self.val = s
        if s in ["+", "-", "*", "/", "Power_xa", "Power_ax", "Log", "Sin", "Cos"]:
            # 运算符
            self.type = 1
        elif s in ["x", "e", "pi"]:
            # 未知数和常数
            self.type = 2
        else:
            # 立即数
            self.type = 0

    # 运算符求目
    def opernum(self):
        if self.type != 1:
            raise Exception("not oper")
        if self.val in ["Sin", "Cos"]:
            return 1
        return 2

    # 转前缀表达式
    # 最后的结果要去除最尾逗号
    def __str__(self):
        if self.type == 1:
            if self.opernum() > 1:
                s = "{},{}{}".format(self.val, self.left, self.right)
            else:
                s = "{},{}".format(self.val, self.left)
        else:
            s = self.val + ","
        return s

    # 转中间表达式
    def mid(self, n, m):
        # 深度限制
        if n == m:
            return "j"

        if self.type == 1:
            if self.val in ["+", "-", "*", "/"]:
                s = "{} {} {}".format(self.left.mid(n+1, m), self.val, self.right.mid(n+1, m))
            elif self.val in ["Power_xa", "Power_ax"]:
                s = "{} ^ {}".format(self.left.mid(n+1, m), self.right.mid(n+1, m))
            elif self.val == "Log":
                s = "log({}, {})".format(self.left.mid(n+1, m), self.right.mid(n+1, m))
            else:
                s = "{}{}".format(self.val.lower(), self.left.mid(n+1, m))
        else:
            s = self.val

        return "(" + s +")"

pfunc = func

def parseinit(s):
    global pfunc
    pfunc = s

def parse(parent):
    global pfunc
    if pfunc is None:
        return

    idx = pfunc.find(",")
    if idx != -1:
        x = pfunc[:idx]
        pfunc = pfunc[idx + 1:]
    else:
        x = pfunc
        pfunc = None

    n = Node(x)
    if n.type == 1:
        # 若是运算符则递归解析
        parse(n)

    if parent is not None:
        if parent.left is not None:
            if parent.right is not None:
                raise Exception("must die")
            else:
                parent.right = n
        else:
            parent.left = n
            if parent.type == 1 and parent.opernum() > 1:
                # 双目则继续右边
                parse(parent)

    return n

import base64

def b62dec(b):
    b = b.replace("9D", "=").replace("9C", "/").replace("9B", "+").replace("9A", "9")
    return base64.b64decode(b)

def b62enc(b):
    b = base64.b64encode(b)
    return b.replace("9", "9A").replace("+", "9B").replace("/", "9C").replace("=", "9D")

微积分

从流程上看,好像关键就是求这个 微分 的逆转了,好像对 F 求积不就拿到 f 了吗?

 

其实这是作者故意给出的陷阱,不过同时,作者也给出了糖果:

  1. 仔细分析表达式 f 以及 F,发现第一字节(最外层运算符)均为 ‘/’(除法)
  2. f = u / v
  3. 结合除法的求导公式
  4. 你会发现其实 f_(也就是 F)中,是包含原 f 组成部分 u v
  5. 使用代码验证设想并输出原始 u v
def check_uv():
    tree = parse(None)

    # 先输出至多 3 级表达式
    print tree.mid(0, 3)
    # 输出
    # (((j * j) - (j * j)) / ((j * j) ^ (2)))
    # 满足求导公式

    # 进一步验证满足条件,分子中的 v 与分母中的 v 相等
    assert str(tree.left.left.right) == str(tree.right.left)

    u = tree.left.right.left
    v = tree.right.left
    # 输出 u v
    print str(u)[:-1]
    print str(v)[:-1]
  1. 这么一来,微积分到此结束

化简求繁

从上一章看,好像提取出 u v 就能直接得出 f 了,其实不行,具体就是从原始的 dec2 中搜不到 v

 

那么,定义上章获得的为 u_ v_

 

从 dec0(以原 u 开头) 和 u_ 对比来看,发现 dec0 到 u_ 进行了一种化简(还好 dec0 短,全手动),具体如下:

  1. *,Log,x,x, log(x,x) 为 1,所以这一段可以直接搜索全删
  2. 剩下的 Log,x,x 替换为 1
  3. +,g(x),g(x) 化简为 *,g(x),2
  4. *,g(x),g(x) 化简为 Power_xa,g(x),2

将得到的 dec0_ 与 u_ 对比,得到 code 的开头处,为 ',pi,' 与之前的推理一致

 

将 dec2 替换 *,Log,x,x,,分析 dec2 与 code 开头处部分(关键字 38,故意调整了对齐)
对比

 

得到要补充的部分,刚好 60 字节
code = ",pi,75,Log,*,-,Power_ax,23,-,Power_xa,+,Log,Power_ax,90,Log,"

 

使用代码求得 ipt
print b62enc(b62dec(enc0[:-2]) + code + b62dec(enc1_2[1:]))[1000: 1080]


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2018-12-23 00:31 被kkHAIKE编辑 ,原因:
收藏
点赞7
打赏
分享
最新回复 (3)
雪    币: 1470
活跃值: (74)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
新月之铭 2018-12-23 14:15
2
0
厉害,这个队有配合
雪    币: 10845
活跃值: (1049)
能力值: (RANK:190 )
在线值:
发帖
回帖
粉丝
看场雪 3 2018-12-23 21:53
3
0
有团,才是队!
雪    币: 73
活跃值: (893)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hixhi 2018-12-26 17:03
4
0
不懂数学,CTF都参加不了了
游客
登录 | 注册 方可回帖
返回