#### 出题思路关键位置是一个vm， 是五月做出来的一个简单实现函数调用的vm小项目改出来的题目，六月以后的两次比赛看到了golang的web服务二进制文件逆向，也学习了下，于是增加了些难度，将原本的vm更改为一个c语言的接口， 由golang调用，而外层的golang则通过gin实现了一个web服务，跑在本地 localhost:8000/ ，可以直接使用浏览器访问，于是应该要先逆golang，也算简单，然后check位置，调用cgocall的参数是c接口，指针指向应该要调用的函数，c写的，然后分析是一个vm， 逆出来opcode，然后逻辑也比较简单，就是异或和减法。> 应该，也算是比较有意思的题目吧，> 题解部分的目录：![](https://i.loli.net/2020/07/08/jHGOMK6U2ebTzQV.png)
#### 题解下载题目， elf 64位f文件， 运行一下， 是这个样子， 打印了一堆东西， 大致是说一个叫gin的引擎，
![](https://i.loli.net/2020/07/08/cKnB294Ya6jROLp.png)同时应该是看到了upx， 然后直接upx -d就ok，
#### gin -路由 参数
然后ida里， 可以知道是个golang写的， 恢复下符号， 直接可以看到main_main， 里面也是多个符号都是`github_com_gin_gonic_gin_`的字样， 其实和前面这个gin的字样对的上， 可以查一下github， 这个是一个golang的web服务框架， 这个似乎是github上也是比较高星的golang的web框架， 然后可以简单看到相关的教程等，
应该先看看，相关路由， 对应参数， 先了解一下怎么玩嘛。
在二进制文件中， 处理符号以后基本可以看到相关的包和函数， 接着恢复恢复字符串， 大体都如下面这个例子，有路由组`/v3`， 路由地址`/check` ，还有个指针指向对应处理函数。![](https://i.loli.net/2020/07/08/jWQCydifzwLrtb5.png)
于是基本可以恢复出来路由，
```python/  			main_main_func1
/v1 			main_main_func2		/login  main_main_func3
/v2			main_main_func4		/user   main_main_func5		/flag   main_main_func6
/v3  			main_main_func7		/check  main_main_func8```
注意这个， gin对于路由组也可以设置对应函数， 然后进入这个路由组先执行， 这样实现一定的权限控制， 所以可以得到上面这样的路由，
而且可以得到端口：
![](https://i.loli.net/2020/07/08/uOyf6iSnrB9xl7o.png)
然后可以考虑， 调试和逆一下参数，
这里简单说几个函数的意义：
func1  就是打印一段信息， 我们进入对应端口可以得到：
func3 :  可以看到`DefaultQuery`函数， 这个是指定参数并且没有参数会给一个默认值， 参数是name。后面是解析为json然后写入到文件`'./config'`内，然后写入以后有一个提示信息。
func4: 这是进入/v2路由组时运行的函数， 判断文件./config是否存在， 如果存在则继续向下进入对应路由。
func5: 这个没有参数， 主要是将一堆东西写入了个文件， 然后打印出提示信息，这个是对应提示和文件内容。
![](https://i.loli.net/2020/07/08/Vo5y7lXbvCKTd4Q.png)
func5： 参数为flag， 写入到hjkl文件内，![](https://i.loli.net/2020/07/08/d2Y317895hHMzEf.png)
fun7: 检测是否存在asdf 和hjkl两个文件，如果存在则进入路由组v3
func8: 路由组v3中的check， 调用了一个函数，然后根据这个函数的返回值，回显错误或者正确，这里就是check的位置。
![](https://i.loli.net/2020/07/08/q7eut5f3UBPwMTY.png)
#### check
然后去看看这个check函数的逻辑好了， 
![](https://i.loli.net/2020/07/08/qNEC83YPdejKrow.png)
cgocall， go调用一个c语言的接口， 参数中的指针是真正会调用的函数， 然后主要看到是函数machine在进行验证， 这些都是c语言写的了， 可以直接反编译出来，
![](https://i.loli.net/2020/07/08/IfNVzoBTR18QqGn.png)
里面调用的函数，首先是调用check_fun1， 参数为'./asdf'文件名， 主要是打开了这个文件并处理， 
check_fun2函数主要进行了一些初始化。
![](https://i.loli.net/2020/07/08/w8AbWJZYvgO2UEI.png)
然后是check_fun3函数，进去是个for循环，check_fun5只是取值， 然后check_func6里面是一个switch循环，这里看出来是一个虚拟机， 
然后调试基本可以看出来一些栈， 栈顶指针之类的，一个基于栈的虚拟机， 
主要写几个重要的位置， 在这个vm中，也比较有意思的东西，
#### function
可能会比较奇怪的两个指令调用， chunk_func9, 
![](https://i.loli.net/2020/07/08/RZKpPhFM4drDxV9.png)
10号指令， 这里面是在保存栈当前的ip等信息， 然后更新了opcode和栈的内容， 是一个函数调用的实现， 
11号指令是一个函数返回， 恢复之前调用时保存下来的栈等数据，
然后关于函数运行的opcode其实是根据10号指令的参数去在func_list中查找的， 
这个func_list在处理文件时建立， 文件中， -1到-2之间定义为一个函数，在一串虚拟指令里，从fun[0] 到fun[n]排开， 默认从fun[0]开始运行，并后面的调用都是依据这个号检索到func_list中对应的函数。
#### register
既然有了函数的话， 也就做了个传参的东西，不过做的也是挺简陋的， 
主要就是两个全局变量，reg_a和reg_b， 然后将栈顶值复制给reg_a/b， 和将reg_a/b压入栈， 就相当于一个传参和传返回值了，
这里的20,21指令是关于reg_a的， 26,27号指令是reg_b，  
![](https://i.loli.net/2020/07/08/EFJytIgA5WQRjYv.png)
#### flag处理
这里简单写一下对于字符串的处理。
首先读入flag，原本是scanf后面改成了读文件内的，都是字符串。然后将flag处理到一个数组内，这个数组会保持以1结束，这样配合后面有个检查长度的实现，
#### break
不知道有师傅用到没有， 其实是一个小的辅助， 这个28号指令就不做任何事情，可以在ida这里下断，然后在opcode的文件'./asdf'中， 某些位置插入这个指令，用来快速断到相关的位置，![](https://i.loli.net/2020/07/08/apFuYxKbcf69m5U.png)
当然，这里也就是辅助调试的一个位置，无关解题。在opcode中也没出现。
#### 返回值
这个位置分散的函数太多了， 关于check以后返回正确与否的问题， 使用了个全局变量，虚拟机会赋值这个变量， 最后返回到go的部分会把这个值返回过去。29号指令。
#### opcode
这里简单列出来所有opcode和参数，
```ctypedef enum {    nop,    	 // 0			停止虚拟机运行    pop,     	 // 1			弹出栈顶值(栈顶指针-1)    push,    	 // 2+var		将var压栈    jmp,    	 // 3+var		跳转到相对偏移var的位置(var可以为负数，为向前跳转，一般为循环的结构)
    jmpf,  	 // 7+var		如果栈顶值不为0则跳转到偏移var的位置    cmp,  	 // 8+var		比较栈顶值和var并将结果0/1压入栈。
    call,        // 10+index	        按照index索引调用对应的函数    ret,         // 11			函数返回
// 基于栈的运算=> 将栈顶两值弹出，运算，结果压栈    add,         // 12			加法    sub,         // 13			减法
    xor,         // 17			异或     read,        // 18			从文件./hjkl中读入flag，并转化到一个数组内。    printc,      // 19			putchar(TOP1)    stoA,        // 10			reg_a = TOP1    lodA,        // 21			push reg_a    pushflag,    // 22			push flag[TOP1]    popflag,     // 23			flag[TOP2] = Top1    Dpush,       // 24			push TOP1
    stoB,        // 26			reg_b = TOP1    lodB,        // 27			push reg_b    nopnop,      // 28			调试辅助指令    set_var_ret  // 29 		赋值整个check的返回值， ret=reg_a} opcode;```
#### 逻辑
这里写一下opcode和对应的逻辑：
```python# -1 10 1 10 4 10 5 2 1 20 10 3 11 -2 def main():	fun1()	fun4()	fun5()	fun3(1)
# -1 18 11 -2 def fun1():	read_flag()
# -1 2 1 22 8 1 7 6 1 2 1 12 3 -11 1 2 1 13 20 11 -2 def fun2():	a = 1	while(flag[a] != 1):		a += 1	return a-1;
# -1 29 0 11 -2 def fun3(reg_a):	ret = reg_a	exit()
# -1 10 2 21 8 22 7 5 2 2 20 10 3 1 2 1 8 23 7 9 24 20 10 6 2 1 12 3 -13 11 -2 def fun4():	a = fun2()	if (a != 22):		fun3(2)	for i in range(1, 23, 1):		fun6(i)
''' -1 2 1  26 2 73   20 10 7 2 2  26 2 89   20 10 7 2 3  26 2 70   20 10 7 2 4  26 2 84   20 10 7 2 5  26 2 -111 20 10 7 2 6  26 2 116  20 10 7 2 7  26 2 103  20 10 7 2 8  26 2 124  20 10 7 2 9  26 2 121  20 10 7 2 10 26 2 102  20 10 7 2 11 26 2 99   20 10 7 2 12 26 2 42   20 10 7 2 13 26 2 124  20 10 7 2 14 26 2 77   20 10 7 2 15 26 2 121  20 10 7 2 16 26 2 123  20 10 7 2 17 26 2 43   20 10 7 2 18 26 2 43   20 10 7 2 19 26 2 77   20 10 7 2 20 26 2 43   20 10 7 2 21 26 2 43   20 10 7 2 22 26 2 111  20 10 7 11 -2 '''def fun5():	fun7(1, 73)	fun7(2, 89)	.......
# -1 21 22 2 122 17 23 11 -2 def fun6(a):	flag[a] ^= 122 
# -1 10 8 27 22 21 13 8 4 7 5 2 2 20 10 3 11 -2 def fun7(reg_b, reg_a):	a = fun8(reg_a)	if (flag[reg_b] - a != 4):		fun3(2)
# -1 21 2 108 17 20 11 -2 def fun8(reg_a):	return reg_a ^ 108# -2 ```
其实就是异或和减法， 用函数调用打乱了一些而已
#### flag：
```pythonarr = [73,89,70,84,-111,116 ,103,124,121,102,99,42,124,77,121,123,43,43,77,43,43,111]
print(bytes(map(lambda x: (((x ^ 108) + 4) ^ 122), arr)))```
![](https://i.loli.net/2020/07/08/uqFPoQXhL3vBJt5.png)
![](https://i.loli.net/2020/07/08/wk3qmsjBegl47vI.png)
