20180CTF g0g0g0 writeup
一道很好的题目,从逆向到数学密码到工具
贴几个writeup吧,比较难找:
https://www.jianshu.com/p/d906619a01b7
https://github.com/xiaohuajiao/my-ctf/tree/master/2018/0ctf/g0g0g0
http://westerns.tokyo/writeups/0ctf2018quals.html#g0g0g0
上面的链接,把solve.py改成solve.sage,linux下装个sage,直接跑就可以得到结果
当得到了数学表达式后,可以从这个网站上直接得到结果:
首先是go语言的逆向
重点是如何得到数据的处理流程以及函数的逻辑
Entering main.main at /tmp/gogo.go:172:6.
.0:
t0 = new string (sa)
t1 = new string (sb)
t2 = new string (sc)
t3 = new [1]interface{} (varargs)
t4 = &t3[0:int]
t5 = make interface{} <- string ("Input 3 numbers":string)
*t4 = t5
t6 = slice t3[:]
t7 = fmt.Println(t6...)
t8 = new [1]interface{} (varargs)
t9 = &t8[0:int]
t10 = make interface{} <- *string (t0)
*t9 = t10
t11 = slice t8[:]
t12 = fmt.Scanf("%s":string, t11...)
t13 = new [1]interface{} (varargs)
t14 = &t13[0:int]
t15 = make interface{} <- *string (t1)
*t14 = t15
t16 = slice t13[:]
t17 = fmt.Scanf("%s":string, t16...)
t18 = new [1]interface{} (varargs)
t19 = &t18[0:int]
t20 = make interface{} <- *string (t2)
*t19 = t20
t21 = slice t18[:]
t22 = fmt.Scanf("%s":string, t21...)
t23 = *t0
t24 = func6(t23)
t25 = *t1
t26 = func6(t25)
t27 = *t2
t28 = func6(t27)
t29 = len(t24)
t30 = t29 == 0:int
if t30 goto 1 else 4
.4:
t43 = len(t26)
t44 = t43 == 0:int
if t44 goto 1 else 3
.3:
t41 = len(t28)
t42 = t41 == 0:int
if t42 goto 1 else 2
.2:
t36 = new [1]int (slicelit)
t37 = &t36[0:int]
*t37 = 0:int
t38 = slice t36[:]
t39 = func1(t24, t38)
t40 = t39 <= 0:int
if t40 goto 5 else 8
.8:
t74 = new [1]int (slicelit)
t75 = &t74[0:int]
*t75 = 0:int
t76 = slice t74[:]
t77 = func1(t26, t76)
t78 = t77 <= 0:int
if t78 goto 5 else 7
.7:
t69 = new [1]int (slicelit)
t70 = &t69[0:int]
*t70 = 0:int
t71 = slice t69[:]
t72 = func1(t28, t71)
t73 = t72 <= 0:int
if t73 goto 5 else 6
.6:
t50 = func2(t24, t26)
t51 = func2(t24, t28)
t52 = func2(t26, t28)
t53 = func4(t50, t51)
t54 = func4(t53, t24)
t55 = func4(t50, t52)
t56 = func4(t55, t26)
t57 = func4(t51, t52)
t58 = func4(t57, t28)
t59 = func2(t56, t58)
t60 = func2(t54, t59)
t61 = new [1]int (slicelit)
t62 = &t61[0:int]
*t62 = 10:int
t63 = slice t61[:]
t64 = func4(t51, t52)
t65 = func4(t50, t64)
t66 = func4(t63, t65)
t67 = func1(t60, t66)
t68 = t67 == 0:int
if t68 goto 9 else 11
.11:
t84 = new [1]interface{} (varargs)
t85 = &t84[0:int]
t86 = make interface{} <- string ("Wrong! Try again!!":string)
*t85 = t86
t87 = slice t84[:]
t88 = fmt.Println(t87...)
jump 10
.10:
return
Leaving main.main.
下面这个.11的函数块是判断某个数位是否大于等于10的:
.11:
t33 = &t3[t31]
t34 = *t33
t35 = t34 >= 10:int
if t35 goto 13 else 10
下面这个.5的函数块是得到某个字符串的长度的
.5:
t9 = len(t3)
t10 = t9 - 1:int
t11 = slice t3[:t10]
t12 = len(t11)
jump 10
t31是当前位置,t36是t31之后的一个位置,t37和t38是取值,看到/10和%10,结合这个写法,可以猜测是大整数的进位
.13:
t36 = t31 + 1:int
t37 = &t3[t36]
t38 = &t3[t31]
t39 = *t38
t40 = t39 / 10:int
t41 = *t37
t42 = t41 + t40
*t37 = t42
t43 = &t3[t31]
t44 = *t43
t45 = t44 % 10:int
*t43 = t45
jump 10
在程序里看到了这个,但是发现,t12,t17,t22在最后的验证过程中并没有使用到
16103 : t12 = fmt.Scanf("%s":string, t11...)
19526 : t17 = fmt.Scanf("%s":string, t16...)
21608 : t22 = fmt.Scanf("%s":string, t21...)
可以写一个很简单的小工具,对main中的所有函数调用截取出来分析:
numberid = 0
f = open('trace.log')
for lines in f:
numberid += 1
if 'func1(' in lines:
print numberid,':',lines
得到这些数据:
29452 : t2 = func0(t0, t1)
24374 : t39 = func1(t24, t38)
24430 : t77 = func1(t26, t76)
24486 : t72 = func1(t28, t71)
32573 : t67 = func1(t60, t66)
24538 : t50 = func2(t24, t26)
24722 : t51 = func2(t24, t28)
24907 : t52 = func2(t26, t28)
29045 : t59 = func2(t56, t58)
29447 : t60 = func2(t54, t59)
25026 : t53 = func4(t50, t51)
25822 : t54 = func4(t53, t24)
26991 : t55 = func4(t50, t52)
27497 : t56 = func4(t55, t26)
27908 : t57 = func4(t51, t52)
28442 : t58 = func4(t57, t28)
29997 : t64 = func4(t51, t52)
30531 : t65 = func4(t50, t64)
31741 : t66 = func4(t63, t65)
24133 : t24 = func6(t23)
24250 : t26 = func6(t25)
24292 : t28 = func6(t27)
根据数值的传递关系,t23,t25,t27,t63为四个未知数,func6函数块应该为大数的初始化
Leaving main.func1, resuming main.main at /tmp/gogo.go:236:13.
t68 = t67 == 0:int
if t68 goto 9 else 11
.11:
t84 = new [1]interface{} (varargs)
t85 = &t84[0:int]
t86 = make interface{} <- string ("Wrong! Try again!!":string)
*t85 = t86
t87 = slice t84[:]
t88 = fmt.Println(t87...)
这一段说明,t67应该等于0,那么就是func1(t60,t66)=0,容易猜测是比较函数 分析某一个func2函数块:
Leaving main.func0, resuming main.func2 at /tmp/gogo.go:52:18.
t3 = t2 + 1:int
t4 = make []int t3 t3
t5 = len(t4)
jump 1
.1:
t6 = phi [0: 0:int, 9: t22] #carry
t7 = phi [0: -1:int, 9: t8]
t8 = t7 + 1:int
t9 = t8 < t5
if t9 goto 2 else 3
.2:
t10 = len(a)
t11 = t8 < t10
if t11 goto 4 else 5
.4:
t12 = &a[t8]
t13 = *t12
jump 5
.5:
t14 = phi [2: 0:int, 4: t13] #a_i
t15 = len(b)
t16 = t8 < t15
if t16 goto 6 else 7
.6:
t17 = &b[t8]
t18 = *t17
jump 7
.7:
t19 = phi [5: 0:int, 6: t18] #b_i
t20 = t14 + t19
t21 = t20 + t6
t22 = t21 / 10:int
t23 = t21 >= 10:int
if t23 goto 8 else 9
.8:
t24 = t21 % 10:int
jump 9
.9:
t25 = phi [7: t21, 8: t24] #tmp
t26 = &t4[t8]
*t26 = t25
jump 1
会发现之后都是重复的东西,重点关注的应该是.7这个小部分,明显是加法!
Entering main.func4 at /tmp/gogo.go:104:6.
.0:
t0 = len(a)
t1 = len(b)
t2 = t0 + t1
t3 = make []int t2 t2
t4 = len(t3)
jump 1
.1:
t5 = phi [0: -1:int, 2: t6]
t6 = t5 + 1:int
t7 = t6 < t4
if t7 goto 2 else 3
这里的.0已经知道了,是大整数乘法 所以,我们知道了func2是大整数加法,func1是大整数比较,func4是大整数乘法
然后得到表达式:
a/(b+c)+b/(c+a)+c/(a+b)=10
这里可以参考知乎:史上最贱的数学题,看上去简单的式子其实本质上是个椭圆曲线
题目总结:
把写程序想象成一棵代码树,编译器的地方是树根,会先找到main,然后main作为子树的树根,继续调用其它函数
这个题的逆向思路:把树前序遍历一下,就可以理解了,就是程序往哪里走,下一条指令就是什么。把树状的框架变成了线性的