Rating:

#### 出题思路
关键位置是一个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和参数,

```c
typedef 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:

```python
arr = [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)