看雪CTF.TSRC 2018 团队赛 第十一题『伊甸园』 解题思路

发布者:Editor
发布于:2018-12-23 18:53



第十一题《伊甸园》在今天(12月23日)中午12:00 结束攻击!仅4支团队攻击成功,其中,金左手 以48489s 攻速夺得本题第一名!


本题结束后,防守团队排行榜如下:




最新赛况战况一览


第十一题之后,攻击方最新排名情况如下:



中午放题搬砖狗哭哭 依然在Top 10 的首席座位,AceHub下降一名至第五名,萌新队上升一名至第四名,其他排名不变。


距离比赛结束,仅剩4题,潜力团队是时候登场了!来“hack"这个局势吧!


第十一题 点评


crownless:

“伊甸园”此题涉及到了数学中的微分和积分知识,但不完全是单纯的微积分,还要求破解者利用数学基础知识对反编译代码化繁为简,并结合Base62编码求出最终答案。



第十一题 出题团队简介


出题团队: CR_Reborn 


夏娃吃了苹果后,发现吃亏了,于是想报复蛇

她摘了一个苹果,掰开蛇的口,硬塞了进去

蛇开始发生蜕变

苹果经过之处,或延长,或缩短,或复制,或变异,或新生,或消失

......

一切过去之后,一具受到惩罚的蛇尸摆在了伊甸园里

它原本长什么样?已经变得印象模糊了

夏娃害怕了后悔了,但已无力挽回

这时亚当说,能把蛇复原的,唯有上帝的神力




第十一题 设计思路


由看雪论坛ElssZion 原创



题目答案:


BpLDc1LExvZywqLC0sUG9A3ZXJfYXgsMjMsLSxQb3dlcl9A4YSwrLExvZyxQb3dlcl9AheCw5MCxMb2c


这道题的设计思路,取自《孙子兵法·谋攻》


孙子曰:夫用兵之法,全军为上,破军次之。是故百战百胜,非善之善也;不战而屈人之兵,善之善者也。故善用兵者,屈人之兵而非战也,此谋攻之法。知可以战与不可以战者胜,识众寡之用者胜。此知胜之道也。


我们的程序里存储着一个残缺的数学函数 f,还有一个数学函数 g;


破解者输入的数据会用于补全 f;


如果 f 的微分结果 与 g 相匹配,解题成功,这时程序会输出字符串,告知破解者已破解成功。


下面,我们给出正确的积分过程和每个步骤所应用的数学公式:


《恢复辅助项》和《积分步骤》这2个文件详细描述了从 g 推导出 f 的过程。


其中,每个步骤所使用到的数学公式 被记录在了《恢复依据公式》和《积分依据公式》2个文件中。


正确的积分结果 f 记录在《公式》中。


f 经过 base62 编码的结果储存在 《编码后公式》 中。


为了补齐 f 所需的正确输入序列号 记录在 《key》 中。


祝大家破解顺利!



原文链接:

https://bbs.pediy.com/thread-248030.htm



第十一题  伊甸园 解题思路


本题解析由看雪论坛 kkHAIKE原创。



程序流程


用 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]


这么一来,微积分到此结束。


化简求繁


从上一章看,好像提取出 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]


原文链接:

https://bbs.pediy.com/thread-248585.htm



第十二题【移动迷宫】正在火热进行中

第12题/共15题

《 移动迷宫》将于 12月25 日中午 12:00 结束

赶紧参与进来吧~!


腾讯安全应急响应中心 

TSRC,腾讯安全的先头兵,肩负腾讯公司安全漏洞、黑客入侵的发现和处理工作。这是个没有硝烟的战场,我们与两万多名安全专家并肩而行,捍卫全球亿万用户的信息、财产安全。一直以来,我们怀揣感恩之心,努力构建开放的TSRC交流平台,回馈安全社区。未来,我们将继续携手安全行业精英,探索互联网安全新方向,建设互联网生态安全,共铸“互联网+”新时代。



转载请注明:转自看雪学院



看雪CTF.TSRC 2018 团队赛 解题思路汇总: 













声明:该文观点仅代表作者本人,转载请注明来自看雪