从一道题看cbc攻击-HITCONCTF2017-SecretServer

栏目: 编程工具 · 发布时间: 6年前

内容简介:xman处看到一题特别有意思的题目,问了下老师,恍然大悟。题目源码:

从一道题看cbc攻击-HITCONCTF2017-SecretServer

前话

xman处看到一题特别有意思的题目,问了下老师,恍然大悟。

题目

题目源码:

import os, base64, time, random, string
from Crypto.Cipher import AES
from Crypto.Hash import *

key = os.urandom(16)

def pad(msg):
    pad_length = 16-len(msg)%16
    return msg+chr(pad_length)*pad_length

def unpad(msg):
    return msg[:-ord(msg[-1])]

def encrypt(iv,msg):
    msg = pad(msg)
    cipher = AES.new(key,AES.MODE_CBC,iv)
    encrypted = cipher.encrypt(msg)
    return encrypted

def decrypt(iv,msg):
    cipher = AES.new(key,AES.MODE_CBC,iv)
    decrypted = cipher.decrypt(msg)
    decrypted = unpad(decrypted)
    return decrypted

def send_msg(msg):
    iv = '2jpmLoSsOlQrqyqE'
    encrypted = encrypt(iv,msg)
    msg = iv+encrypted
    msg = base64.b64encode(msg)
    print msg
    return

def recv_msg():
    msg = raw_input()
    try:
        msg = base64.b64decode(msg)
        assert len(msg)<500
        decrypted = decrypt(msg[:16],msg[16:])
        return decrypted
    except:
        print 'Error'
        exit(0)

def proof_of_work():
    proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in xrange(20)])
    digest = SHA256.new(proof).hexdigest()
    print "SHA256(XXXX+%s) == %s" % (proof[4:],digest)
    x = raw_input('Give me XXXX:')
    if len(x)!=4 or SHA256.new(x+proof[4:]).hexdigest() != digest: 
        exit(0)
    print "Done!"
    return

if __name__ == '__main__':
    proof_of_work()
    with open('flag.txt') as f:
        flag = f.read().strip()
    assert flag.startswith('hitcon{') and flag.endswith('}')
    send_msg('Welcome!!')
    while True:
        try:
            msg = recv_msg().strip()
            if msg.startswith('exit-here'):
                exit(0)
            elif msg.startswith('get-flag'):
                send_msg(flag)
            elif msg.startswith('get-md5'):
                send_msg(MD5.new(msg[7:]).digest())
            elif msg.startswith('get-time'):
                send_msg(str(time.time()))
            elif msg.startswith('get-sha1'):
                send_msg(SHA.new(msg[8:]).digest())
            elif msg.startswith('get-sha256'):
                send_msg(SHA256.new(msg[10:]).digest())
            elif msg.startswith('get-hmac'):
                send_msg(HMAC.new(msg[8:]).digest())
            else:
                send_msg('command not found')
        except:
            exit(0)

这里我们能够得到的信息是:

一个固定的iv: 2jpmLoSsOlQrqyqE
一个已知的明文: Welcome!!

返回并得到加密之后的结果,而我们可以看到解密的结果是这样组成的。

MmpwbUxvU3NPbFFycXlxRc/vKRt4fANSEpCk0agly4E=

base64解密之后得到的是iv+decryption:

2jpmLoSsOlQrqyqE + D(Welcome!!)

如果最后后端解密出来的明文中存在某些特定的命令,就能跳转到相应的函数,所以如果我们能够控制解密出来的明文,那么就能执行任意命令了。那么如何做到呢?

精彩的异或执行任意命令

到了最精彩的地方了,注意别眨眼!

  • D(密文) = “Welcome!!” ^ IV1
  • 明文 = D(密文) ^ IV2
  • 明文 = “Welcome!!” ^ IV1 ^ IV2
    其中”Welcome!!” 和 IV1无法改变,所以如果能改变IV2,就能改变明文。这时候我们这么操作:

IV2 = “Welcome” ^ IV1 ^ “控制的字符串”

这样明文就会变成”控制的字符串了,具体实现函数为:

def strxor(str1, str2):
  return ''.join([chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(str1, str2)])
def flipplain(oldplain, newplain, iv):
  """flip oldplain to new plain, return proper iv"""
  return strxor(strxor(oldplain, newplain), iv)
get_flag_iv = flipplain(pad("Welcome!!"), pad("get-flag"), iv_encrypt)
msg = base64.b64encode(get_flag_iv + base64.b64decode(msg)[16:])

最后的结果用中间量输出,可以看出已经成功的将最后解密明文控制住了。(太精彩了这一步):

从一道题看cbc攻击-HITCONCTF2017-SecretServer

下面根据最后的命令执行:

if msg.startswith('exit-here'):
    exit(0)
if msg.startswith('get-flag'):
    send_msg(flag)
elif msg.startswith('get-md5'):
    send_msg(MD5.new(msg[7:]).digest())
elif msg.startswith('get-time'):
    send_msg(str(time.time()))
elif msg.startswith('get-sha1'):
    send_msg(SHA.new(msg[8:]).digest())
elif msg.startswith('get-sha256'):
    send_msg(SHA256.new(msg[10:]).digest())
elif msg.startswith('get-hmac'):
    send_msg(HMAC.new(msg[8:]).digest())
else:
    send_msg('command not found')

如果还是没理解,建议对照下面的图再重新理一遍思路。

从一道题看cbc攻击-HITCONCTF2017-SecretServer

unpad非法截断枚举flagMd5得flag

我们已经能得到flag的加密的值了。但是这还不够,还得得到flag的明文。这个时候去关注其他的命令,观察到 send_msg(MD5.new(msg[7:]).digest()) 这条命令,他最后能将解密出来的字符串除去前七位的md5的值而flag的类型为hitcon{},前七位刚好是 hitcon{ ,我们知道aes加解密过程是不依赖于iv的,而 get-md5 正好是7位,也就是说我们按照上面的思路:

new_iv = flipplain(pad("hitcon{"), pad("get-md5"), iv_encrypt)
msg = base64.b64encode(new_iv + cipher_flag + last_byte_iv + cipher_welcome)

最后能得到’get-md5’+flag[7:]的加密值,跳进去get-md5命令之后就能得到MD5(flag[7:])的值,不过这还不够,我们观察:

def unpad(msg):
    return msg[:-ord(msg[-1])]

unpad函数并没有校验msg[-1]是否合法,所以我们根据这个bug函数+aes的分组链接模式(下一个密文解密的iv由上一个密文充当,为此深夜打扰了一波iromise),所以我们可以接着拼接一段 iv2+cipher ,让iv2[-1]足够大,大到从末端切割掉flag[7:]的尾巴,假设切割到只剩下一位,则我们可以得到 MD5(flag[7:8]) ,然后我们暴力枚举,发送字符串到服务器,匹配正确的密文就能找到flag[7:8]。同理得到flag[7:8]之后开始爆破flag[7:9],因为这时候你知道了flag[7:8],实际上也只要爆破一位,以此类推就能得到flag。具体代码见:

https://github.com/ctf-wiki/ctf-challenges/blob/master/crypto/blockcipher/padding-oracle-attack/2017_hitcon_secret_server/exp.py

后话

(tips: 其中52-53行的 new_iv = flipplain("hitcon{".ljust(16, 'x00'), "get-md5".ljust(16, 'x00'), iv_encrypt) 可以替换成我上面说的 new_iv = flipplain(pad("hitcon{"), pad("get-md5"), iv_encrypt) ,因为iv并不影响aes解密过程,所以也无妨,只能说liubaozheng老师想的更加细致了。)


以上所述就是小编给大家介绍的《从一道题看cbc攻击-HITCONCTF2017-SecretServer》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机程序设计艺术(第3卷)-排序和查找(英文影印版)

计算机程序设计艺术(第3卷)-排序和查找(英文影印版)

(美)Donald E.Knuth / 清华大学出版社 / 2002-9 / 85.00元

《计算机程序设计艺术排序和查找(第3卷)(第2版)》内容简介:这是对第3卷的头一次修订,不仅是对经典计算机排序和查找技术的最全面介绍,而且还对第1卷中的数据结构处理技术作了进一步的扩充,通盘考虑了将大小型数据库和内外存储器。它遴选了一些经过反复检验的计算机方法,并对其效率做了定量分析。第3卷的突出特点是对“最优排序”一节作了修订,对排列论原理与通用散列法作了全新讨论。一起来看看 《计算机程序设计艺术(第3卷)-排序和查找(英文影印版)》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器