CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard

申博_新闻事件 申博 225次浏览 已收录 0个评论

媒介

近来,为了防备成天着迷switch智力下降,因而刷了一道有意思的Crypto题,个中触及也许2个考点:Hash Length Extension Attacks & CBC Byte Flipping Attack,虽然应用有些贫苦,然则磨炼脑力,从CTF最先~

顺序剖析

先看主顺序代码:

if __name__ == '__main__':
    unprintable = b""
    for i in range(256):
        if chr(i) not in string.printable:
            unprintable += bytes([i])
    alarm(60)
    s = Sign(urandom(16), urandom(16))
    while True:
        print("Choose:\n[1] Register\n[2] Login")
        op = input()
        if op == '1':
            user = input("Input your username(hex): ")
            token = s.register(bytes.fromhex(user))
            if not token:
                print("Sorry, invalid username.")
            else:
                print("Your token is: %s" % token.hex())
        elif op == '2':
            token = input("Input your token: ")
            res = s.login(bytes.fromhex(token))
            if not res:
                print("Sorry, invalid token.")
            elif not res[1]:
                user = res[0].hex()
                print("Sorry, your username(hex) %s is inconsistent with given signature." % user)
            else:
                user = res[0].strip(unprintable).decode("Latin1")
                print("Login success. Welcome, %s!" % user)
                if user == "admin":
                    print("I have a gift for you: %s" % FLAG)
        else:
            print("See you")
            break

大抵分为两局部:

1.注册功用输入用户名,顺序管帐算出一个token给你。

if op == '1':
            user = input("Input your username(hex): ")
            token = s.register(bytes.fromhex(user))
            if not token:
                print("Sorry, invalid username.")
            else:
                print("Your token is: %s" % token.hex())

2.将token输入让顺序校验。

py
 elif op == '2':
            token = input("Input your token: ")
            res = s.login(bytes.fromhex(token))
            if not res:
                print("Sorry, invalid token.")
            elif not res[1]:
                user = res[0].hex()
                print("Sorry, your username(hex) %s is inconsistent with given signature." % user)
            else:
                user = res[0].strip(unprintable).decode("Latin1")
                print("Login success. Welcome, %s!" % user)
                if user == "admin":
                    print("I have a gift for you: %s" % FLAG)

若是署名准确且用户名为admin则能够获得flag。

我们继承跟进函数看一下:

起首是token的天生体式格局。

def register(self, username):
        if b'admin' in username:
            return None
        sig = md5(self.salt + username).digest()
        padlen = self.block - len(username) % self.block
        username += bytes([padlen] * padlen)
        iv = urandom(self.block)
        aes = AES.new(self.key, AES.MODE_CBC, iv)
        c = aes.encrypt(username)
        return iv + c + sig

发明顺序不允许注册admin用户,然后token分为3局部:`iv + c + sig`

iv为随机数`urandom(self.block)`,署名sig为用户名加盐的hash值`sig = md5(self.salt + username).digest()`,密文c为AES加密获得。

再看解密体式格局:

def login(self, cipher):
        if len(cipher) % self.block != 0:
            return None
        self.T -= 1
        iv = cipher[:self.block]
        sig = cipher[-self.block:]
        cipher = cipher[self.block:-self.block]
        aes = AES.new(self.key, AES.MODE_CBC, iv)
        p = aes.decrypt(cipher)
        p = p[:-p[-1]]
        return [p, md5(self.salt + p).digest() == sig]

解密只对密文c举行了操纵,而且还多了一步:

p = p[:-p[-1]]

这只是一步罕见去除padding的操纵,无需剖析。

进击点思索

那末下面思索怎样举行进击。

起首明白我们可控参数:注册时的username和登录时的token。

视线定位到解密流程,大抵分为3步:

1.解密后res是不是一般

2.解密后署名是不是准确

3.解密后username是不是即是admin

不难发明,我们解密的3局部`iv+c+sig`,个中c和sig都要校验,而iv却没有任何的校验。

这不由让我们想到了一些进击思绪,比方掌握iv,举行cbc字节翻转进击,令c解密后获得admin的明文。

那末思绪就接二连三,当我们掌握iv,使c转变后,sig也得响应转变。

p = aes.decrypt(cipher)
p = p[:-p[-1]]
return [p, md5(self.salt + p).digest() == sig]

那末sig怎样展望晓得username=admin时刻的sig呢?

这里我们注意到署名体式格局:

sig = md5(self.salt + username).digest()

很明显相符我们的hash长度拓展进击需求。

起首是salt的长度:

s = Sign(urandom(16), urandom(16))

我们发明key和salt的长度都是16

我们又可控username,那末我们能够应用hash拓展进击盘算username=admin时的sig MD5值。

那末如今思绪变得对照清楚:

1.应用已知长度的salt和已知username=admin举行hash长度拓展进击盘算sig_new

2.应用原有Iv和c,举行cbc字节翻转进击获得iv_new,使得c解密获得admin

3.获得新的token:iv_new+c+sig_new

然则随后我又堕入僵局,我没法盘算出md5(secret+m),此时只晓得secret的长度,而且请求m=admin

此时发明症结代码:

user = res[0].strip(unprintable).decode("Latin1")

以是我们的解密效果不是必需即是admin,前后有弗成见字符也行,比方:

\x00\x00\x00\x80\x00\x00admin\x00\x00\x00

CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard

————————————-

申博网络安全巴士站

申博-网络安全巴士站是一个专注于网络安全、系统安全、互联网安全、信息安全,全新视界的互联网安全新媒体。

————————————-

在个中的都会被过滤掉,以是末了获得的username照样admin

hash长度拓展进击

显现这不是一个简朴的hash长度进击。

好比我们第一次注册用户名skysky,获得:

CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard

此时sig为:

eb1d2538fcb11aff70aa21213e7ddba9

若是我们此时去举行hash长度拓展进击:

import hashpumpy
tmp = hashpumpy.hashpump('eb1d2538fcb11aff70aa21213e7ddba9', 'skysky', 'admin', 16)
print tmp

我们能够获得效果:

('0931bd0b164be725a1eba43031642e43', 'skysky\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x00\x00\x00\x00\x00\x00\x00admin')

但这不是我们要的效果,此时明文块除admin还带有可显字符skysky。

我们须要将username掌握为弗成显字符,比方:

import hashpumpy
tmp = hashpumpy.hashpump('eb1d2538fcb11aff70aa21213e7ddba9', '\x00', 'admin', 16)
print tmp

获得效果:

('0931bd0b164be725a1eba43031642e43', '\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00admin')

然则如许又有新的问题,我注册的username长度只需1,密文块长度只需16,而这里我要解密成的效果长度却有53。

那无论怎样cbc翻转也弗成能到达我们的目标,以是这里的username还不止是为弗成显字符这么简朴,我们这里经心组织一下:

起首我们组织一个以下字符串:

s="010101010101010101010101010101018000000000000000000000000000000000000000000000000001000000000000".decode("hex")
s+="\xff"*16+"admi"+chr(ord(n)^1)

我们用这个字符串去举行cbc翻转进击,获得一个适宜的iv+c(虽然顺序iv会变,但key不会变,cbc自身须要掌握iv,以是iv会变不妨),然后我们get解密效果。

\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x14\xff\xca\x8a\xb6\xc3\xd2\x1d\x07\xd7\x16\x14\x86\xe1\x17\xb9admin

(注:这里须要爆破n轮,由于我们虽然能举行cbc翻转进击,然则无法掌握全部解密内容,以是不克不及包管进击以后还满是弗成见字符)

获得解密效果后,我们再依据此时的状况组织hash长度拓展进击,盘算出sig。

\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x14\xff\xca\x8a\xb6\xc3\xd2\x1d\x07\xd7\x16\x14\x86\xe1\x17\xb9admin

CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard

我们能够发明上下一致,我们胜利能够应用hash长度拓展进击展望署名值,以是只需注册用户名:

01010101010101010101010101010101

便可。

CBC字节翻转进击

CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard

CBC翻转进击的精华都在这张图里,我们只须要相识以下公式:

组织的iv[1]= 本来的iv[1]^plain[1]^’a’

本题中,我们可控iv,username也是本身注册的

010101010101010101010101010101018000000000000000000000000000000000000000000000000001000000000000ffffffffffffffffffffffffffffffff61646d696f

CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard

我们只需让末了的o举行cbc翻转进击,酿成n便可。

比方我们获得token:

2d74da0969b99075b213e470b907f8e11d82f656d676080efda01be5b75941f147673b8e05686f98d2f1f6acccea2f0bf02a2bd1de59b70b31d84ebbc0dcec84ad6a8adab3589c919db153b98b9f4879ad492c39690116bbbc2f9e55d7e0cc0635e32ea23bbeb431b5c710094591d6fd

去掉背面的sig获得:

2d74da0969b99075b213e470b907f8e11d82f656d676080efda01be5b75941f147673b8e05686f98d2f1f6acccea2f0bf02a2bd1de59b70b31d84ebbc0dcec84ad6a8adab3589c919db153b98b9f4879ad492c39690116bbbc2f9e55d7e0cc06

去掉iv获得c:

1d82f656d676080efda01be5b75941f1
47673b8e05686f98d2f1f6acccea2f0b
f02a2bd1de59b70b31d84ebbc0dcec84
ad6a8adab3589c919db153b98b9f4879
ad492c39690116bbbc2f9e55d7e0cc06

我们的明文m为:

01010101010101010101010101010101
80000000000000000000000000000000
00000000000000000001000000000000
ffffffffffffffffffffffffffffffff
61646d696f

末了一组只需5位,须要添补11,即\x0b*11

那我们即调解c的第4个block对应n的地位便可。

ad6a8adab3589c919db153b98b9f4879

编写剧本:

py
token = token.decode('hex')
cipher1 = token[:-16]
cipher2 = cipher1[:-32]+cipher1[-32:-28]+chr(ord(cipher1[-28])^1)+cipher1[-27:-16]+cipher1[-16:]

getflag

晓得道理后便可编写剧本以下:

#!/usr/bin/python2
from Crypto.Cipher import AES
from hashlib import md5
from os import urandom
import string
from libnum import *
import os
from pwn import *
import hashpumpy
 
con = remote("47.95.212.185" ,38611)
def register(username):
    con.sendlineafter("Login\n","1")
    con.sendlineafter("(hex): ",username.encode("hex"))
    con.recvuntil("is: ")
    return con.recvline().strip()
    
def encrypt(username):
    token = register(username).decode("hex")
    return token[:-16]
 
def decrypt(cipher):
    cipher +="a"*16
    con.sendlineafter("Login\n","2")
    con.sendlineafter("token: ",cipher.encode("hex"))
    con.recvuntil("(hex) ")
    data = con.recvuntil("admin".encode("hex"))
    return data.decode("hex")
 
s="010101010101010101010101010101018000000000000000000000000000000000000000000000000001000000000000".decode("hex")
target = ord("n")
mask = 1
assert(target^mask != target)
flag = True
cnt = 0
s+="\xff"*16+"admi"+chr(target^mask)
while(flag):
    cnt+=1
    if(cnt %200 ==0):
        print cnt
    flag = False
    cipher1 = encrypt(s)
    cipher2 =cipher1[:-32]+cipher1[-32:-28]+chr(ord(cipher1[-28])^mask)+cipher1[-27:-16]+cipher1[-16:]
 
    pp = decrypt(cipher2)
    pp = pp.strip("admin")
    pp = pp[-16:]
    for x in pp:
        if x in string.printable:
            flag = True
            break
s = decrypt(cipher2)
name ="\x01"*16
sig1 = register(name)[-32:]
tmp = hashpumpy.hashpump(sig1, '01010101010101010101010101010101'.decode('hex'), s[48:], 16)
sig2=tmp[0]
context.log_level="debug"
con.sendlineafter("Login\n","2")
payload = cipher2.encode("hex")+sig2
con.sendlineafter("token: ",payload)
con.interactive()

运转获得flag:

hgame{hard_cryptooooo!}

CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明CTF问题实战:2019-Hgame-Week4-Crypto&Sign_in_SemiHard
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址