CTF训练

百度杯十一月第三场Reverse专题

字号+ 作者:i春秋研习社 来源:转载 2017-02-12 17:17 我要评论( )

1、Misc XX 题目给了一串字符串LNalVNrhIO4ZnLqZnLpVsAqtXA4FZTEc+ 根据题目名称,了解这是XXcode编码,使用在线解码网站:http://web.chacuo.net/charsetxxencode......

1、Misc XX
题目给了一串字符串LNalVNrhIO4ZnLqZnLpVsAqtXA4FZTEc+
根据题目名称,了解这是XXcode编码,使用在线解码网站:http://web.chacuo.net/charsetxxencode/
2、Misc 贝丝家族
这个题目说得很清楚,base,再看字串,MZWGCZ33MVZGQZLJL5STQOJTGRPWK4SVJ56Q====。4个=号,明显base32,py解之。
base64.b32decode('字符串')
3 Misc 敲击
键位编码或加密,用26键键盘打一遍就知道了。 结果是:flag{xvzoc}

4、Reverse CrackMe01
Windows程序的消息处理,显示flag的方式不是弹窗,而是新建一个窗口将文字写到中
 
通过ShowWindow 定位到进行判断的函数
可以发现这里的 DrawTextW 就是将chText打印到程序中央。
可以看到这里有一堆Unicode编码的 文本。然后在程序上往上翻‘
这里对chText按位用同一个字符进行异或。 我选择了直接爆破。
 

5、Reverse CrackMe02
这 题没怎么用ida,就是开始看了下导入函数,发现窗口文本获取函数GetDlgItemTextA只有一个地方调用,而且调用地方很是可疑。 GetWindowTextA虽然也只有一个地方调用,但显然没用。 然后就直接上了OD,bpx GetDlgItemTextA下断,f9运行,输入点“checksn",预期中断下:
获取输入并检查是否为空,然后直接以16进制形式(未转换)进入栈区内存,地址为堆栈地址=0018F618,再进行AES解密,解密结果存放地址为堆栈地址=0018F400。再看看AES解密:

先进行AES分组解密,密钥为B1nGzL[4st-TeAm],IV向量16进制为 000102030405060708090A0B0C0D0E0F,使用CBC模式。如果输入转成的16进制长度不是16的倍数,则分组觖密后剩下的会 进行ECB模式的解密,密钥不变。 回去主流程,接着看。

AES 解密之后的字串再进行base64解码,地址00401990 ,然后然后取字串奇数位,检查其值不能大于'S',再然后根据其-0x32的值查跳转表,实现swich选择。其原值必须为"82QS"中的一个,不然就 是进入default,这是一条死亡之路。 switch中的call循环做完之后,在0040273A 检查0018F1E4 是否为1,为1就game over。

再检查堆栈0018F2EC处字串是否为空,即第一个字符不为0x0,为0x0则直接弹出弹窗,没有有用信息。再下面就是对0018F2EC处字串与一定值异或,最后在弹窗中显示。

于是又进行了异或的爆破,结果很容易挑选:The flag is right!。参与运算的定值为0x2f。

那这个0x2f是哪来的呢,这就要说说4个case中的call了。依据奇数位字符选择switch case,其右边的偶数位字符-0x30,结果进行累加,最后累加值就是0x2f。

case 中的call就不看了。看了也是各种混乱,明白程序意图后再看比较清晰。4个case call的作用有些类似,都是以偶数位减-0x30的值为循环次数控制,循环比较内存指点地址处的值是否为0,如果不是就OVER。如果我们定内存显示宽 度为16字节,具体内存取数情况为:8,2保特横向位置不变,纵向分别随着循环向上一格和向下一格;Q,S保持纵向位置不变,横向分别向左一格和向右一 格。这似乎像是矩阵的行列操作。我们再看下取数处的内存,调整下内存视图看就很明白。

看到这张图就明白了吧,C3为终点,另一端为起点,只要顺着0走就不会错,只要跟到此处,不难发现第一 个switch选择必须为2,其它的顺路走就OK。但是整完发现,路径虽没错,可是0x2f的累加值才到0x2e,还差1。再看看S的case call,与其余三个有点不同:

在00402923处,就是比较取值是否为0的,别的call检查失败直接置标志返回,这里跳到了0040293B,检查取数是否为0xFF,如果是直接返回,不置失败标记。正好到终点处右边为0xff,且S循环一次是右移一格,取数,所以最后加上S1即可。

最后把取得数base64编码再AES加密就可得到flag。

路径选择结果为27S281S182S327S287S323Q124S281S1,共32字节,base64后是44字节,填充到48字节进行AES加密。不知道为什么答案会给出填充到64字节,不懂。

此题多解原因是如果偶数为0x30,则不进行循环,0x2f累加值也为零,对最后的正确流程没有影响。所以是可以随意附加"20"、"80"、"Q0"、'S0",从而产生多解。 最后附上加密脚本:

[AppleScript] 纯文本查看 复制代码
?
1
2
3
4
5
6
7
8
<font style="color:rgb(62, 62, 62)">from Crypto.Cipher import AES
key = 'B1nGzL[4st-TeAm]'iv = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F'
path = '27S281S182S327S287S323Q124S281S1'.encode('base64').strip()
m = path +'\x00'*4cryptor = AES.new(key,AES.MODE_CBC,iv)
cryptortext = cryptor.encrypt(m)
print'==================================================='
print 'cryptortext:'+cryptortext.encode('hex')
print'==================================================='</font>

6、Reverse CrackMe03

试运行下,Console程序,输入错误直接退出。 直接AD载入,查找字串,发现input your key:,直接下断,运行,断下,发现程序主要流程全在这一个函数里。心中忽然有种轻松的感觉。

输 入完成后,计算常量b1ngzl的长度,再计算输入长度,接着初始化一段栈空间,前面计算的两个长度xor 4。接着在00E91221调用call 00E91020(这个程序主流程很明晰,所以没关掉动态基址,见谅,后四位偏移应该是一样的),在内存中填充一大组数据,填充规则是写入5*9*9个 dword,进行了9*9 81个循环,每次循环按规则写入5个dword,第一个dword始终填充0xFFEEFFEE,第二个dword在前9次循环填充 0xEFEFEFEF,其它填充0xFEFEFEFE,第三个第三个dword全部填充用0xFEFEFEFE,第四个dword在循环次数为9倍数+1 时(第一次也算)时填充0xEFEFEFEF,其它填充0xFEFEFEFE 。

然后按b1ngzl换算的偏移地址填充0xEEFFEEFF,共计填充6次,填充地址为00E91221处call操作的内存区域。

可 以看下这个偏移计算4*5*(c1+9*c2)+0x3c,0x3c为指向刚才填充区域相对栈指针的偏移量,4是dword 4字节对齐,那5*(c1+9*c2)与前面5*9*9相对应的话,那9*c2就是纵坐标了,c1就是横坐标了。其实到这里已经很明显了,5*9*9实际 上就是5个9*9的二维数组,这6次循环填充是操作的第一个9*9数组。

大 家也应该注意到00E91246和00E91259的注释,这里做了处理防止数组越界。0x00--0xff,最终都会落在(9,9)范围内。处理过程 中,比较第一个字符b,即0x62,那就在第一个数组a1[6][2]处填充0xEEFFEEFF。如果记5个数组分别为 a1,a2,a3,a4,a5,0xFFEEFFEE为0,0xEEFFEEFF为1,0xEFEFEFEF为2,0xFEFEFEFE为3,则现在5个 数组情况如下(实际上就是个矩阵嘛):

我们再接着往下看。
a1:                                          
   0 0 0 0 0 0 0 0 0
   0 0 0 0 0 0 0 0 0
   0 0 0 0 0 0 0 0 0
   0 1 0 0 0 0 0 0 0
   0 0 0 0 0 0 0 0 0
   0 0 0 0 0 0 0 0 0
   0 0 1 1 0 1 0 1 0
   0 1 0 0 0 0 0 0 0
   0 0 0 0 0 0 0 0 0
a2:
   3 3 3 3 3 3 3 3 3
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
a3:
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
a4:
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
   3 2 2 2 2 2 2 2 2
a5:
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
   2 2 2 2 2 2 2 2 2
我们再接着往下看。
 

开始先是一个输入处理的循环,前半部分和上一段的代码一样,就是根据输入的字符定位数组元素位置,这里注意下,循环次数为输入长度与4异或的值。

再看下面的数据操作。在00E912E3、00E912F8、00E91315、00E9132F处的比较,实际上是分别是后面四个数组相应坐标的元素与定值比较。

ecx 就是数组坐标,上面已经说过了。*4是dword 4字节对齐,如果不考虑对齐 ecx*4+4实际上就是ecx+1,ecx是定位a1[x][y],那ecx+1就是a2[x][y],因为数据存放是5*9*9,81个循环每次写入 5组数据,上面已经提前伏笔了。所以00E912A0 到00E91354就是循环根据输入字符0xHL(H表示高4位数值,L表示低4位数值),检查a2[H][L]、a3[H][L]、a4[H][L]、 a5[H][L]处的值是否为0xFEFEFEFE,即上面数组中的3,如果不是,则将a1相应位置的值进行异或。

再 看看这5个异或都操作a1的什么位置。ecx*4-0xb4去除字节对齐干扰除以4,就是ecx-0x2d,后面的0x2d十进制是45,排除其它四个数 组干扰除以5就是9,也就是a1[H][L]-9,换成数组坐标是a1[H-1][L],其它三个分别是a1[H+1][L]、a1[H][L-1]、 a1[H][L+1],从图上看是a1[H][L]的上下左右,分别使用a2[H][L]、a3[H][L]、a4[H][L]、a5[H][L]元素作 改写开关。具体操作内容是a1[H-1][L]与0x11111111异或,在数组a1里实际上就是0,1翻转,而其余三个位置则是翻转a1[H-1] [L]的值。而比较检查,可以对照上面后四个数组的情况,很容易就看出来当a1[H][L]落在第一行时,a1[H-1][L]是不操作的,因为越界 了;a1[H][L]落在第一列时a1[H][L-1]是不操作的。这个我就看不懂了,为什么不相应检查落在最右侧的情况,所以如果落在最右侧,那操作 a1[H][L+1]变成操作a1[H+1][0]。最下侧就没有必要检查了,因为我们输入有字符到不了那里,同样顶端的检测也是没有必要的,因为我们的 输入也定位不到哪。

00E9135A到00E913B7就是将第一个数组中的数值按dword相加,所以溢出是不管的,总和与0xFC9CFC4D比较,为真则提示This Flag is right!

按照这个条件,我们很容易就能把第一个数组中的0xEEFFEEFF和0xFFEEFFEE的个数算出来。来个小爆破吧。

[AppleScript] 纯文本查看 复制代码
?
1
2
3
4
5
6
7
8
a1 = 0xEEFFEEFF
   a2 = 0xFFEEFFEE
   sum = 0
   for i in range(82):  
      sum = (a1*i+a2*(81-i))&0xFFFFFFFF
      if sum == 0xFC9CFC4D:     
       print '0xEEFFEEFF real count:',i    
      return i

结果是唯一的,0xEEFFEEFF的个数是15。所以只要保证经过输入处理后,a1中1的数目为15就成功。我们 看看输入之前a1的情况已经在上面列出。输入的处理仔细想下,其实如果a1[H][L]不落在最右侧,那4个检查条件是不受影响的,那每次操作数值翻转是 1:3的关系,输入处理之前已经有了6个1,再来9个1就行。如果a1[H][L]的上下左右都为0,那在此位置进行两次翻转,其结果就是:
x 0 x
1 x 1
x 1 x
来三次就ok了。
9*9的空间很大,很容易就可以找 到很多3个翻转结果0 1为1:3关系。但是,但是,但是啊,这是6次循环,循环次数为输入字符长度xor 4。6 xor 4 =2。那后4次取值就越界了。所以我们要找到输入长度xor 4后小于输入长度,并且异或后结果为偶数的。为什么要是偶数,自己想吧,该说的我已经全说了啊。又是脚本小跑下,找了两个比较小的数12,14,异或后值 为8,10。当然这些不是必要条件。比如:
4477vvvv1234
12位输入,8次循环操作,如果定位点上方为0,每两次同位置的操作翻转结果0 1为1:3关系。三个位置就是9个1,开始的6个1没有影响到,所以是15个1,完全符合条件。后面4位根本不参加运算,所以随便填。两个地方都可以多解,数量非常巨大的多解。
上面一段说的偶数啊、同位置操作两次啊,这些是为了最简单寻找答案,看下面这些也行
4477T1111111 uJKK111123=1234
为了这个题,我把算法都还原了,反正已经写了,贴出来给大家看看。把各部分写成了函数调用
def cal_num():       #爆0xEEFFEEFF个数
a1 = 0xEEFFEEFF
a2 = 0xFFEEFFEE
sum = 0
for i in range(82):    sum = (a1*i+a2*(81-i))&0xFFFFFFFF
   if sum == 0xFC9CFC4D:      print '0xEEFFEEFF real count:',i      return idef create_table():       #5*9*9初始列表
l = []  for i in range(9):    for j in xrange(9):
     l.append(0xFFEEFFEE)      if i == 0:
       l.append(0xFEFEFEFE)      else:
       l.append(0xEFEFEFEF)
     l.append( 0xEFEFEFEF)      if j == 0:
       l.append(0xFEFEFEFE)      else:
       l.append(0xEFEFEFEF)
     l.append( 0xEFEFEFEF)  print '0xFEFEFEFE Pos:',
indx = 0
for i in range(l.count(0xFEFEFEFE)):
   indx = l.index(0xFEFEFEFE,indx)    print indx,
   indx +=1
print '\n'
return ldef hl(s):         #坐标      
bh = (s>>4&0xf)%9
bl = (s&0xf)%9
return 5*(bh*9+bl)def cal_1(table):  
                                                                #a1 初次循环改写
b = 'b1ngzl'
off_b = []  for i in range(len(b)):
   inx = hl(ord(b))
   off_b.append(inx)  
   table[inx] = 0xEEFFEEFF
print '0xEEFFEEFF Pos:',off_bdef cal_2(table,str,num):  #使用str改写a1
count = len(str)  for i in range(count):
   idx = hl(int(str))    if table[idx+1] != 0xFEFEFEFE:
     table[idx-45] = table[idx-45]^0x11111111
   if table[idx+2] != 0xFEFEFEFE:
     table[idx+45] = table[idx-45]^0x11111111
   if table[idx+3] != 0xFEFEFEFE:
     table[idx-5] = table[idx-45]^0x11111111
   if table[idx+4] != 0xFEFEFEFE:
     table[idx+5] = table[idx-45]^0x11111111
if table.count(0xEEFFEEFF) == num:  
                                              #为15,cal_num()计算结果
   print "The flag is right!"
else:    print "Wrong!!!"if __name__ == '__main__':
num = cal_num()
table_l = create_table()  
cal_1(table_l)
flag = raw_input('input the flag:')  
count = len(flag) ^ 4
cal_2(table_l,list(flag[:count]),num)

本文来自: 蜗蜗侠's Blog-关注网络安全 http://blog.icxun.cn/Note/CTF/542.html

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 百度杯十一月第二场WriteUp

    百度杯十一月第二场WriteUp

    2017-02-12 17:19

  • 百度杯十二月第四周Blog挑战题WriteUp

    百度杯十二月第四周Blog挑战题WriteUp

    2017-02-12 17:12

网友点评
暂时未开启评论功能~
精彩导读