ezCM
直至比赛结束,这道题目都是 0 解题,一方面是因为比赛时间较短,另一方面还是因为这道题目较难,考察了不常见的椭圆曲线算法(ECC),大大增加了对做题者的要求。
题目信息
题目是使用 Golang 来编写的一个 CrackMe 程序,程序内符号没有被去除,所以这篇文章就不会讲解如何恢复 Golang 程序符号,另外 IDA Pro 7.6 已经支持 Golang 程序分析,打开就可以直接恢复被去除的符号信息。
题目要求打开一个 KeyFile ,并且通过读取其文件的内容来注册程序,我们要做的就是通过分析程序验证方式来编写一个 KeyFile,使其可以通过程序注册验证,最终拿到 flag 数据。
前置知识
由于是 Golang 的题目,在一些数据结构和调用约定上和大多数语言都不一样,所以一定不能过于的依赖伪代码,在调试过程中最好能够多关注汇编代码,这样在逆向过程中会快速掌握到核心。这部分内容参考学习了 panda0s – Golang underlying data representaion ,本来是不想把这部分内容放在这篇文章中的,但是由于关联性过大,所以不得不拿来饱满文章内容。
函数调用
在函数调用的过程中,无论是调用参数还是返回值都是通过栈来传递。
其传参的特征是
- 参数传递顺序是从右往左传递,而且不使用像是 push pop 这样的操作栈的指令,而是直接对栈上的内容进行修改。
- 参数传递一般都是借助一个寄存器中转,例如
rax、rcx,先把数据原来的储存位置的数据赋值到这个寄存器上,然后再把这个寄存器的内容赋值给栈上数据,并且如果数据是 0x10
size 的结构体,就会借助 xmm 寄存器中转来加速。
其返回值的特征是
- 返回值的位置紧贴着在最后一个参数的地址之后。以上图为例,最后一个参数的地址是 rsp + 0x250 – 0x248 + 0x8 =
rsp + 0x10,所以这里的返回值的地址就是在 rsp + 0x250 – 0x238 = rsp +
0x18,有多个返回值的情况也是类似。
方法调用
在上图中,严格意义上并不是一次函数调用,而是一次方法调用。他是对 MyMainWindow 这个对象下的 Academy 方法进行了调用,这个传入的参数就是这个对象的指针,像是 this 一样。这个对象的指针就相对于函数调用的第一个参数。
String 字符串
String 结构
struct String{
char * strPtr;
int64 size;
}
所以 Golang 程序在传递字符串的时候,同时也会在后续跟一个参数,这个参数指的就是字符串的长度。同时由于这样的机制,使得字符串的内容在内存中分布不需要截止符’\x00’
Slice 切片
在其他语言中(例如 python),Slice 是一种切片的操作,切片之后可以返回一个新的数据对象,但是 Golang 中的 Slice 不仅仅是一种切片的操作,更像是一种灵活的数据结构。
了解 Slice 结构后,在 IDA 中修改对应的变量类型,可以大大加快分析速度。
Slice 结构
struct slice {
dq Pointer;
dq Length;
dq Capacity;
}
Pointer:指向 Slice 底层数组的元素开始位置的指针
Length:Slice 的当前长度
Capacity:Slice 底层数组的最大长度,超过此长度会自动扩展
初始化 Slice
my_slice := make([]int, 3,5)
这表示先声明一个长度为 5、数据类型为 int 的底层数组,然后从这个底层数组中从前向后取 3 个元素作为 slice 的结构(length = 3,cap = 5)
make 最底层调用 runtime_makeslice 分配空间,这个函数返回的是指向内部数组的指针
访问 Slice
org_len := slice1[name_size + 1]
在访问 Slice 中元素时,会检测是否越界如果越界则调用 runtime_panicIndex
append / copy
当 Length 已经等于 Capacity 的时候,再使用 append 给 slice 追加元素,会调用 runtime_growslice 进行扩容。
在代码中的表现是在 append / copy 的时候会检测,slice.len + 1 与 slice1.cap 的大小关系
如图在把 slice 转换为字符串的过程前,由于将要 copy slice,所以会对传参 len 进行检测。
切片截取
myvar