一道很好的题目,从逆向到数学密码到工具
贴几个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作为子树的树根,继续调用其它函数
这个题的逆向思路:把树前序遍历一下,就可以理解了,就是程序往哪里走,下一条指令就是什么。把树状的框架变成了线性的