Fanxs's Blog

某CTF在线培训平台Reverse题

字数统计: 2k阅读时长: 7 min
2018/07/26 Share

这几天客户要参加一个CTF竞赛,据说是在一个CTF在线培训平台上抽题来做赛题,就让我们把题做了之后给他们培训CTF。这个平台里只有一道reverse的题,名字还是取了最近特别火的电影“我不是药神”之名(这电影巨好看,业界良心),这几天虽然忙着测一个智能设备,但还是抽出时间想来试试这道reverse题。

0x00. 初探

题目
这道题为一个EXE文件,打开后需要输入名字和key来通关,如果输错了,则会“爆炸”。迫不及待打开了exe,瞎填了名字和key,想看看报错的语句是什么(方便后来ida里寻找)。

刚输完名字,程序就关掉了……看来是只有输入了固定的名字,才能走到输入key那一步。好吧,那就直接IDA上吧。

0x01. IDA起!找名字!

IDA挂上exe,先来静态分析。打开String窗口,看了一圈发现了明显的输出字段:

直接跳到相关函数处:

这里截取了对名字判断的部分,直接F5看伪代码。这里的consolePrint,stringInit,getInput函数都是我分析了函数功能后加上的重命名。分析中间的循环体,可见对我们输入的name字段和“nsfocus”字符串一位一位作对比,若两个字符串存在不同之处,则break跳出循环,将结果nameFilterResult设置为1,输出“No Auth”后结束进程。显然,合理的名字就只有“nsfocus”了。

那我们给输入”nsfocus”继续往下走:

阔以了~终于让我们走到第二部输入key了,赶紧输一个试试……

???????????????
就这样在目瞪口呆中,虚拟机被关掉了。。。日诶
( 这就是传说中的爆炸吗 Orz…

0x02. IDA跑!找key啊喂

没有一点点防备,也没有一丝顾虑,你就这样关机……为了防止关机,我另起了一个cmd,专门执行shutdown.exe -a来停止关机……(醉 눈_눈

key判断的部分茫茫地长。。


从结果往上推,可见我们需要的是“Congratulation..”的那个结果,要求sub_402BE0函数的返回结果必须为True。这个函数用到了3个参数,v34, key, v48-v19

那我们先从key入手。可见在中间位置,有对key重新赋值的一个循环:

第一个do while循环,用v57取出key里的字符,若字符不为空,则v51指针++,取下一个字符。可见此循环意在将v51推到key的结尾,循环结束时,v51的位置为key的终结符后一位。

因此,for循环的第一个参数 v51-v26-1,则是算出了key的长度了(其实严格来说是 length – 1),将这个值给参数i初始化,之后取出 v24 = key[i],再–i,可见是由尾部向头部取出key的值,再执行以下关键操作:

1
key[i] ^= string[v50 - v24 - 1 - i]^dword_41E378 ^*(&predefinedKey + i % 7)

第一部分即为string[ length – 1 – i],从头开始取出string的值,string[0],string[1],string[2]……dword_41E378则会被每次的计算结果所重新赋值。第三部分,可看到这是个字符数组predefindedKey[7] = “nomoney”,取出i%7位置的值,如果 i为7,则在这个数组里取出”n”,若i为8,则取出”o”,以此类推。

这个循环可初步判断为这道题的关键点,用python可模拟为:

1
2
3
4
5
predefinedKey = [110,111,109,111,110,101,121]     # nomoney
length = len(key)
for i in range(length-1, 0 ,-1):
key[i] = key[i] ^ string[length-1-i] ^ dword ^ predefinedKey[i%7]
dword = key[i]

所以,要掌握key值转换后的值,我们还需要知道 dword 和 string的值。至此,key就先分析到这。

0x03. 关键函数sub_402BE0

做出最后结果的函数sub_402BE0的伪代码是:

这里的伪代码有点乱七八糟的,看起来不明所以。直接看汇编:

这里sub_402BE0函数传的参分别对应ecx,edx,edi,edx即为key。红色框住的部分为函数的主要判断,从汇编代码中我们可一目了然地看到,主体部分在不断地从ecx和edx两个寄存器指向的内存地址中取出字符一位一位做对比,若存在不同之处,则返回失败。根据这段汇编代码,我们可初步猜测,这个函数为key和题目中暗藏的最后的密文做对比,若相同,则返回成功,同时也以此推断,第三个参数edi可能为key的长度。

(PS: 所以很多时候还是看汇编靠谱啊…就是看的头疼了一点

0x04. 是时候让IDA跑起来了!

静态分析得差不多了,走向最终胜利的流程图已分析出来,剩下的就是把我们还未解决的几个参数搞清楚。

至此,走向胜利还需要知道几个参数的值:

  1. dword — 修改key
  2. string — 修改key
  3. v48-v19 –函数sub_402BE0参数,猜测为长度
  4. v34 –函数sub_402BE0参数,猜测为最后的密文

dword位于bss区,程序在跑起来时会在某个函数处赋予它初值。静态分析扫了一下代码,发现生成或影响 string / v34参数值都没有用到Key参数,说明它们是独立于我们的输入的,应该为一个定值。

那我们先跑起来~

挂上debugger后,跑一下….诶?首长,它自杀了。。。进程自己关掉了,啥情况???
再细细排查了一下,发现一个神奇的函数:


原来这还有一个坑,启动程序时检查进程里是否存在peid,idaq,ollydbg等进程,如果存在则退出进程。

好吧,那我就把idaq改个名,就。。好了눈_눈

终于跑起来了,我们通过动态调试,让程序自己生成我们想要的几个参数的值:

打几个断点,记录下我们需要的值,可知

dword的初始值为 2

string 的值为:

1
0x62,0x39,0x62,0x37,0x64,0x64,0x31,0x63,0x34,0x32,0x31,0x65,0x30,0x30,0x35,0x62,0x63,0x39,0x61,0x37,0x66,0x37,0x30,0x62,0x38,0x34,0x38,0x65,0x33,0x64,0x30,0x65


在sub_402BE0函数里打断点,看到我们最后要的密文ciphertext为:

1
0x51,0x3C,0x0f,0x67,0x5c,0x2c,0x41,0x53,0x6a,0x49,0x70,0x51,0x68,0x54,0x2b,0x23,0x5b,0x64,0x0e,0x60,0x6a,0x43,0x69,0x46,0x69,0x0c,0x25,0x41,0x72,0x44,0x16,0x72

同时也在函数中确认了第三个参数的确为Key的长度。再多重新跑程序输入不同的key值,动态调试中发现string,dword和ciphertext并没有改变,说明之前分析出来的结论,它们的值与输入独立不相关是正确的。

至此我们离胜利只有一步之遥。

0x05. 胜利

总结一下,这个程序先对name做判断,若不为nsfocus,则退出进程。

运行时,通过几个函数生成string 和最后的ciphertext (不是个constant string),并和输入的key进行异或和重新赋值。这个重新赋值的过程类似于CBC (Cipher Block Chaining ):

输入key为”ABCDEFGHIJK”时,得到key变换过程:

这类似于CBC block加密的方法即是这里Key的转换过程。为了拿到flag,我们需要一个能转换为指定的ciphertext的一个key值,即根据ciphertext,由以上的类CBC加密结构,解密出key的值。

这里用一个python脚本来进行计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# nomoney的循环字符串
predefinedKey = [0x6e,0x6f,0x6d,0x6f,0x6e,0x65,0x79,0x6e,0x6f,0x6d,0x6f,0x6e,0x65,0x79,0x6e,0x6f,0x6d,0x6f,0x6e,0x65,0x79,0x6e,0x6f,0x6d,0x6f,0x6e,0x65,0x79,0x6e,0x6f,0x6d,0x6f]
# string
string = [0x62,0x39,0x62,0x37,0x64,0x64,0x31,0x63,0x34,0x32,0x31,0x65,0x30,0x30,0x35,0x62,0x63,0x39,0x61,0x37,0x66,0x37,0x30,0x62,0x38,0x34,0x38,0x65,0x33,0x64,0x30,0x65]
# 将nomoney和string做预先的xor结果
string2 = [predefinedKey[-(i+1)]^string[i] for i in range(0,len(predefinedKey))]
# 最后要的密文
ciphertext = [0x51,0x3C,0x0f,0x67,0x5c,0x2c,0x41,0x53,0x6a,0x49,0x70,0x51,0x68,0x54,0x2b,0x23,0x5b,0x64,0x0e,0x60,0x6a,0x43,0x69,0x46,0x69,0x0c,0x25,0x41,0x72,0x44,0x16,0x72]

result = []
dword = 0x02

for i in range(0,len(ciphertext)):
r = string2[i] ^ ciphertext[-(i+1)] ^ dword
dword = ciphertext[-(i+1)]
result.append(chr(r))

key = ''.join(result)
key = key[::-1]
print(key)

这样就可以算出了最后的结果:

CATALOG
  1. 1. 0x00. 初探
  2. 2. 0x01. IDA起!找名字!
  3. 3. 0x02. IDA跑!找key啊喂
  4. 4. 0x03. 关键函数sub_402BE0
  5. 5. 0x04. 是时候让IDA跑起来了!
  6. 6. 0x05. 胜利