【CTF大赛】第五届XMan选拔赛 ezCM Writeup

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 结构

structString{char* strPtr;int64size;}

所以 Golang 程序在传递字符串的时候,同时也会在后续跟一个参数,这个参数指的就是字符串的长度。同时由于这样的机制,使得字符串的内容在内存中分布不需要截止符’\x00’


Slice 切片

在其他语言中(例如 python),Slice 是一种切片的操作,切片之后可以返回一个新的数据对象,但是 Golang 中的 Slice 不仅仅是一种切片的操作,更像是一种灵活的数据结构。

了解 Slice 结构后,在 IDA 中修改对应的变量类型,可以大大加快分析速度。

Slice 结构

structslice {dqPointer;dqLength;dqCapacity;}

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 := slice1[a:b]

myvar 是新一个新的切片结构

dataPtr = &slice1.dataPtr[1],相当于给了一个底层数组的指针

len = b – a

cap = slice1.cap – a

寻找关键函数

对于这种有界面的程序,和常规的只有一个控制台的题目不同的是,题目的关键信息不是直接存在于 main 函数中,所以我们首先要做的就是定位到题目的关键位置。

而在这道题里,我们的突破口就是在没有选择文件的情况下,点击“Register”就会弹出的信息框“Cannot open target file.”

我们可以利用 IDA 的 Shift + F12 热键调出 Strings 窗口来查找“Cannot open target file.”字符串

搜索字符串后,我们再双击进入,在前面自动生成的名称处按下 X 热键查找交叉引用

对于每一处引用我们都前去查看,最终找到了关键的代码位置(截图仅截取部分代码)

我们接下来对函数内容进行分步骤的解析

KeyFile 格式解析

特征格式

这部分内容虽然伪代码看起来混乱,但是大概可以猜测是开头和结尾的特征字符

---BEGIN CERT---xxx----END CERT----

通过这两个特征读取出关键的秘钥信息 xxx,然后传入到后续函数,这样的标记格式在其他地方也很常见,所以这里不着重分析。

解密核心数据

在后续函数中的对秘钥核心数据做了一个解码

这里上述代码中可以看出,程序先通过一个 base64 解密对中间部分内容进行解码,然后以两个字节为一个单位进行解密,对第一字节异或 0xAA 得到数据,并且对第二字节异或 0x55,与第一字节的内容进行比对,如果不同则直接退出。

数据结构格式

解密后的数据是如何在存放的,分别又代表着那些信息?想要知道这些就要分析接下来所做的代码。

代码中由于对切片做了很多索引操作,所以有各种各样的越界检测,我们抛开这部分代码来看,就可以看出 Username 和 Organization 的储存结构 —— 第一个字节存放字符串长度,后续跟字符数据。

根据前置知识中切片的相关知识,这里调用 math_big_nat_setBytes 的切片内容我们可以大致还原,主要就是根据切片的 len 和 cap 来确定,

切片左边的值:由来源切片的 cap 减去的内容

切片右边的值:由来源切片左边的值 + 新切片的 len

所以 main_a 和 main_b 的内容来源于新的切片内容分别是

a[org_len+name_len+2:org_len+name_len+2+8]a[org_len+name_len+10:org_len+name_len+10+8]

这部分内容可以结合上面的伪代码结合得出,也就是 Username 和 Organization 后的 8 字节是 main_a 的内容,再后 8 字节是 main_b 的内容,最后 4 字节是 main_expire 的内容。

这里需要注意的是,main_a 和 main_b 的内容都是以字节的形式直接转换为大数类型,而 main_expire 是以 WORD 的形式读取(使用 2 字节),这两种读取方式的字节序不同。

数据表

综合上面所说的,可以得出以下表格来记录 KeyFile 文件内加密数据格式

验证逻辑

约束条件

了解了程序如何解析 KeyFile 后,接下来才是本文最关键的地方,也就是程序的验证方法。

首先会把 main_a 和 main_b 的内容相加,然后与 13417336609348053335(0xba33f48ee008e957)进行比对,如果相同则进入后续的判定,这就是对 a 和 b 之间关系的一个约束。

初始化大数

接下来又对三个大数进行了初始化,我把这几个常量去 Google 搜索了一下,发现 main_p 的值是在 GF(p) 上的椭圆曲线中常用的一种取值,这对于我们了解接下来的代码的大致内容有所帮助。

这样根据常量来猜测程序意义的方法也是常用的,这里就是借助了椭圆曲线中常见的 p。

验证代码

最终的检测代码就是判断题目 public_key 是否在椭圆曲线(y^2 = x^3 + ax + b)上,其中 a 和 b 是用户可控的值,我们现在有一个在椭圆曲线上的点 生成元 G ,那么我们就可以根据这个 G 点的值和 a + b 的约束来反推 a 和 b 的值。

推导过程只是我粗浅的理解,所以可能不是很规范,但是表明了如何推出 a 的值,有了 a 的值后,我们直接相减就可以计算得到 b 的值。

注册机编写

通过上述的逻辑和整理,我们可以快速的编写出一个 Keygen,我的代码如下




接下来把 KeyFile 拖入程序,点击 Register,即可成功通过验证得到 Flag

可以发现 flag 的值其实就是 main_a 和 main_b 的 hex 编码后的值,这样可以保证 flag 的唯一性。

总结

在这之前其实也遇到过一些 Golang 的题目,但是因为 Golang 难以分析,所以这些题目的核心算法相对来说都比较简单,都是一些比较简单的逻辑问题。这道题虽然分析过程看似简单轻松,但是实际上我对其内涵的原理和程序的用法进行了深入的研究,消耗了大量的时间和精力。虽然本题最终展现的并不是一个 ECC 难题,但是在逆向分析的过程中,我也学习到了一些 ECC 的内涵和代码实现。希望各位师傅可以借此题来开篇学习 Golang 逆向和 ECC 算法。


最后

我整理了相关的资料,有需要的朋友可以私我哦!!!

资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容